Tools: How I built an AI-powered Git context menu for Windows using Tauri and Rust

Tools: How I built an AI-powered Git context menu for Windows using Tauri and Rust

Source: Dev.to

What GitPop does ## Tech stack and why Tauri ## The engineering challenges ## 1. Registering a File Explorer context menu entry (from Rust) ## 2. The “flashing terminal” bug when spawning Git on Windows ## 3. Tauri v2 capabilities, window transparency, and the invisible app trap ## The sparkle button: AI commit generation (locally, by default) ## Try it out As developers, we commit code constantly. The annoying part is that quick commits tend to force a slow workflow: I wanted the best parts of both worlds: visual staging like a GUI, but the speed of a terminal. So I built GitPop: a lightweight Windows File Explorer extension that adds a modern Git commit UI to your right-click menu, with an optional local AI commit generator. From File Explorer, you can right-click inside any repo folder and choose GitPop Here to open a small popup that lets you: The core goal is simple: make the “small commit” workflow as fast as a shell command, but less blind. For a context menu popup, the most important metric is startup time. If right-clicking a folder and selecting GitPop Here takes a noticeable moment, it feels broken. I ruled out Electron because it ships a full Chromium runtime and commonly incurs large binary sizes and heavier memory overhead compared to a native-webview approach. Tauri uses the system webview (WebView2 on Windows) with a Rust backend, which fits the “open instantly” requirement much better. Windows integration looks simple from the outside, but there are a few spicy corners. These were the big three. To show GitPop Here in the right-click menu, GitPop needs to register a command in the Windows Registry. Instead of asking users to run a .reg file (which feels sketchy even when it is not), GitPop can do this via a Rust command in a “Setup Mode”. This uses the winreg crate and registers under: That keeps the install per-user (no admin required) and binds the command to the app executable path: Why this approach works well: GitPop does not use libgit2. Instead, the Rust backend spawns native Git CLI commands like: This is intentional: the Git CLI automatically respects the user’s existing SSH keys, credential helpers, hooks, and global configs. On Windows, though, naïvely calling Command::new("git") can cause a CMD window to flash briefly. It is the kind of micro-annoyance that makes an app feel janky. The fix is to set a Windows-specific process creation flag so child processes run hidden: From there, all Git calls use build_hidden_cmd("git") instead of Command::new("git"). I wanted a transparent, glassy popup. That means: The catch is that Tauri v2 locks down frontend APIs by default. Without the right capability permissions, the window did not crash, it just stayed invisible while the process happily ran in the background. The fix is to explicitly allow the window operations your frontend performs in capabilities/default.json: This is one of those “security first” defaults that is correct, but it will absolutely prank you the first time you try to do anything window-related. Writing commit messages is small, but it adds friction. GitPop’s ✨ Sparkle button reduces that friction: Privacy matters. Shipping proprietary diffs to a cloud API is a non-starter for a lot of dev work. So GitPop defaults to Ollama, running locally. It detects installed models (for example llama3.2 or qwen2.5-coder) and generates commit messages without API keys, paid tokens, or network calls. GitPop also supports OpenAI, Anthropic, Gemini, and custom endpoints for people who prefer hosted models. The model selection is an implementation detail. The UX goal is consistent: stage, sparkle, commit, done. If you are on Windows (Soon OSx) and this fits your workflow, grab the latest installer from the repository: Feedback, issues, and PRs are welcome. I am also exploring what it would take to bring the same “right-click commit UI” to macOS Finder, where the integration constraints are different but the pain is identical. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: use winreg::enums::*; use winreg::RegKey; #[tauri::command] fn install_context_menu() -> Result<(), String> { let hkcu = RegKey::predef(HKEY_CURRENT_USER); let exe_path = std::env::current_exe() .map_err(|e| e.to_string())? .to_string_lossy() .into_owned(); let bg_path = r#"Software\Classes\Directory\Background\shell\GitPop"#; let (bg_key, _) = hkcu .create_subkey(bg_path) .map_err(|e| e.to_string())?; bg_key.set_value("", &"GitPop Here").map_err(|e| e.to_string())?; bg_key .set_value("Icon", &format!("\"{}\"", exe_path)) .map_err(|e| e.to_string())?; let (bg_cmd, _) = bg_key .create_subkey("command") .map_err(|e| e.to_string())?; // %V resolves to the clicked folder path for Directory\Background handlers. bg_cmd .set_value("", &format!("\"{}\" \"%V\"", exe_path)) .map_err(|e| e.to_string())?; Ok(()) } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: use winreg::enums::*; use winreg::RegKey; #[tauri::command] fn install_context_menu() -> Result<(), String> { let hkcu = RegKey::predef(HKEY_CURRENT_USER); let exe_path = std::env::current_exe() .map_err(|e| e.to_string())? .to_string_lossy() .into_owned(); let bg_path = r#"Software\Classes\Directory\Background\shell\GitPop"#; let (bg_key, _) = hkcu .create_subkey(bg_path) .map_err(|e| e.to_string())?; bg_key.set_value("", &"GitPop Here").map_err(|e| e.to_string())?; bg_key .set_value("Icon", &format!("\"{}\"", exe_path)) .map_err(|e| e.to_string())?; let (bg_cmd, _) = bg_key .create_subkey("command") .map_err(|e| e.to_string())?; // %V resolves to the clicked folder path for Directory\Background handlers. bg_cmd .set_value("", &format!("\"{}\" \"%V\"", exe_path)) .map_err(|e| e.to_string())?; Ok(()) } COMMAND_BLOCK: use winreg::enums::*; use winreg::RegKey; #[tauri::command] fn install_context_menu() -> Result<(), String> { let hkcu = RegKey::predef(HKEY_CURRENT_USER); let exe_path = std::env::current_exe() .map_err(|e| e.to_string())? .to_string_lossy() .into_owned(); let bg_path = r#"Software\Classes\Directory\Background\shell\GitPop"#; let (bg_key, _) = hkcu .create_subkey(bg_path) .map_err(|e| e.to_string())?; bg_key.set_value("", &"GitPop Here").map_err(|e| e.to_string())?; bg_key .set_value("Icon", &format!("\"{}\"", exe_path)) .map_err(|e| e.to_string())?; let (bg_cmd, _) = bg_key .create_subkey("command") .map_err(|e| e.to_string())?; // %V resolves to the clicked folder path for Directory\Background handlers. bg_cmd .set_value("", &format!("\"{}\" \"%V\"", exe_path)) .map_err(|e| e.to_string())?; Ok(()) } COMMAND_BLOCK: use std::process::Command; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; #[cfg(target_os = "windows")] const CREATE_NO_WINDOW: u32 = 0x08000000; fn build_hidden_cmd(program: &str) -> Command { let mut cmd = Command::new(program); #[cfg(target_os = "windows")] { cmd.creation_flags(CREATE_NO_WINDOW); } cmd } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: use std::process::Command; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; #[cfg(target_os = "windows")] const CREATE_NO_WINDOW: u32 = 0x08000000; fn build_hidden_cmd(program: &str) -> Command { let mut cmd = Command::new(program); #[cfg(target_os = "windows")] { cmd.creation_flags(CREATE_NO_WINDOW); } cmd } COMMAND_BLOCK: use std::process::Command; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; #[cfg(target_os = "windows")] const CREATE_NO_WINDOW: u32 = 0x08000000; fn build_hidden_cmd(program: &str) -> Command { let mut cmd = Command::new(program); #[cfg(target_os = "windows")] { cmd.creation_flags(CREATE_NO_WINDOW); } cmd } CODE_BLOCK: { "permissions": [ "core:window:default", "core:window:allow-show", "core:window:allow-close", "process:allow-exit" ] } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "permissions": [ "core:window:default", "core:window:allow-show", "core:window:allow-close", "process:allow-exit" ] } CODE_BLOCK: { "permissions": [ "core:window:default", "core:window:allow-show", "core:window:allow-close", "process:allow-exit" ] } - Open a heavy IDE (often just to stage files and write a message), or - Run git add . && git commit -m "fix" in a terminal and hope you remember what changed. - See changed files instantly - Stage and unstage with a clean UI - Review diffs (without switching to a separate app) - Coming soon. - Generate commit messages from staged diffs using local models via Ollama (or your preferred API) - Frontend: React + TypeScript + vanilla CSS (glassmorphism-style dark UI) - Backend: Rust - Framework: Tauri v2 - HKCU\Software\Classes\Directory\Background\shell\GitPop - It is self-contained and reversible - It avoids “copy this registry text and trust me” instructions - It stays compatible with existing Git setups because GitPop does not try to reconfigure Git - git status --porcelain - git diff --cached - git commit -m ... - "transparent": true - Start hidden to avoid a white flash while React loads: "visible": false - Show the window once the UI is ready (window.show()) - Stage files - GitPop runs git diff --cached - The staged diff is sent to an LLM to propose a commit message - GitHub: https://github.com/vinzify/gitpop