Skip to content

Commit 2d8fa04

Browse files
committed
Add alias management features: create, delete, and view aliases with detailed info
1 parent 7a2e898 commit 2d8fa04

3 files changed

Lines changed: 335 additions & 9 deletions

File tree

src-tauri/src/main.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ struct ValidationResponse {
3737
exists: bool,
3838
}
3939

40+
#[derive(Debug, Serialize, Deserialize)]
41+
struct AliasInfo {
42+
name: String,
43+
command: String,
44+
created_by_tool: bool,
45+
}
46+
4047
#[derive(Debug, Serialize, Deserialize)]
4148
struct OpenAIRequest {
4249
model: String,
@@ -244,6 +251,21 @@ async fn create_alias(request: AliasRequest) -> AliasResponse {
244251
match output {
245252
Ok(result) => {
246253
if result.status.success() {
254+
// Mark this alias as created by our tool
255+
let marker_result = Command::new("git")
256+
.args(&[
257+
"config",
258+
"--global",
259+
&format!("alias-tool.{}", request.alias_name),
260+
"git-alias-generator",
261+
])
262+
.output();
263+
264+
if let Err(_) = marker_result {
265+
// If we can't mark it, that's not critical
266+
eprintln!("Warning: Could not mark alias as created by tool");
267+
}
268+
247269
AliasResponse {
248270
success: true,
249271
message: format!("Alias '{}' created successfully!", request.alias_name),
@@ -293,6 +315,74 @@ async fn get_existing_aliases() -> Result<Vec<String>, String> {
293315
Ok(aliases)
294316
}
295317

318+
#[tauri::command]
319+
async fn get_detailed_aliases() -> Result<Vec<AliasInfo>, String> {
320+
let output = Command::new("git")
321+
.args(&["config", "--global", "--get-regexp", "^alias\\."])
322+
.output()
323+
.map_err(|e| format!("Failed to get aliases: {}", e))?;
324+
325+
let aliases_text = String::from_utf8_lossy(&output.stdout);
326+
let mut aliases = Vec::new();
327+
328+
for line in aliases_text.lines() {
329+
if let Some(space_pos) = line.find(' ') {
330+
let alias_part = &line[..space_pos];
331+
let command_part = &line[space_pos + 1..];
332+
333+
if let Some(dot_pos) = alias_part.find('.') {
334+
let alias_name = alias_part[dot_pos + 1..].to_string();
335+
336+
// Check if this alias was created by our tool
337+
let marker_output = Command::new("git")
338+
.args(&[
339+
"config",
340+
"--global",
341+
&format!("alias-tool.{}", alias_name),
342+
])
343+
.output();
344+
345+
let created_by_tool = match marker_output {
346+
Ok(result) => {
347+
let marker_value = String::from_utf8_lossy(&result.stdout);
348+
marker_value.trim() == "git-alias-generator"
349+
}
350+
Err(_) => false,
351+
};
352+
353+
aliases.push(AliasInfo {
354+
name: alias_name,
355+
command: command_part.to_string(),
356+
created_by_tool,
357+
});
358+
}
359+
}
360+
}
361+
362+
Ok(aliases)
363+
}
364+
365+
#[tauri::command]
366+
async fn delete_alias(alias_name: String) -> Result<String, String> {
367+
// Remove the alias
368+
let output = Command::new("git")
369+
.args(&["config", "--global", "--unset", &format!("alias.{}", alias_name)])
370+
.output()
371+
.map_err(|e| format!("Failed to delete alias: {}", e))?;
372+
373+
if !output.status.success() {
374+
let error = String::from_utf8_lossy(&output.stderr);
375+
return Err(format!("Failed to delete alias: {}", error));
376+
}
377+
378+
// Remove the tool marker if it exists
379+
let _ = Command::new("git")
380+
.args(&["config", "--global", "--unset", &format!("alias-tool.{}", alias_name)])
381+
.output();
382+
383+
Ok(format!("Alias '{}' deleted successfully", alias_name))
384+
}
385+
296386
#[tauri::command]
297387
async fn save_settings(state: State<'_, AppState>, key: String, value: String) -> Result<(), String> {
298388
let mut settings = state.lock().unwrap();
@@ -315,6 +405,8 @@ fn main() {
315405
preview_alias,
316406
create_alias,
317407
get_existing_aliases,
408+
get_detailed_aliases,
409+
delete_alias,
318410
save_settings,
319411
get_setting
320412
])

src/App.tsx

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect, useCallback } from "react";
22
import { invoke } from "@tauri-apps/api/tauri";
3-
import { Save, Eye, Settings, GitBranch, AlertCircle, CheckCircle, Loader2 } from "lucide-react";
3+
import { Save, Eye, Settings, GitBranch, AlertCircle, CheckCircle, Loader2, List, Trash2 } from "lucide-react";
44

55
interface GitCommand {
66
name: string;
@@ -21,6 +21,12 @@ interface AliasResponse {
2121
alias_command?: string;
2222
}
2323

24+
interface AliasInfo {
25+
name: string;
26+
command: string;
27+
created_by_tool: boolean;
28+
}
29+
2430
interface Toast {
2531
id: number;
2632
message: string;
@@ -86,7 +92,9 @@ function App() {
8692
const [isLoading, setIsLoading] = useState(false);
8793
const [toasts, setToasts] = useState<Toast[]>([]);
8894
const [existingAliases, setExistingAliases] = useState<string[]>([]);
95+
const [detailedAliases, setDetailedAliases] = useState<AliasInfo[]>([]);
8996
const [showSettings, setShowSettings] = useState(false);
97+
const [showAliasViewer, setShowAliasViewer] = useState(false);
9098

9199
const addToast = useCallback((message: string, type: 'success' | 'error') => {
92100
const id = Date.now();
@@ -126,6 +134,28 @@ function App() {
126134
}
127135
}, []);
128136

137+
const loadDetailedAliases = useCallback(async () => {
138+
try {
139+
const aliases = await invoke<AliasInfo[]>("get_detailed_aliases");
140+
setDetailedAliases(aliases);
141+
} catch (error) {
142+
console.error("Error loading detailed aliases:", error);
143+
addToast("Failed to load aliases", "error");
144+
}
145+
}, [addToast]);
146+
147+
const deleteAlias = useCallback(async (aliasName: string) => {
148+
try {
149+
const result = await invoke<string>("delete_alias", { aliasName });
150+
addToast(result, "success");
151+
await loadDetailedAliases();
152+
await loadExistingAliases();
153+
} catch (error) {
154+
console.error("Error deleting alias:", error);
155+
addToast("Failed to delete alias", "error");
156+
}
157+
}, [addToast, loadDetailedAliases, loadExistingAliases]);
158+
129159
const loadSettings = useCallback(async () => {
130160
try {
131161
const apiKey = await invoke<string | null>("get_setting", { key: "openai_api_key" });
@@ -152,7 +182,10 @@ function App() {
152182
useEffect(() => {
153183
loadExistingAliases();
154184
loadSettings();
155-
}, [loadExistingAliases, loadSettings]);
185+
if (showAliasViewer) {
186+
loadDetailedAliases();
187+
}
188+
}, [loadExistingAliases, loadSettings, loadDetailedAliases, showAliasViewer]);
156189

157190
useEffect(() => {
158191
const timer = setTimeout(() => validateAlias(aliasName), 300);
@@ -240,6 +273,9 @@ function App() {
240273
addToast(result.message, "success");
241274
setPreviewCommand(result.alias_command || "");
242275
loadExistingAliases(); // Refresh the list
276+
if (showAliasViewer) {
277+
loadDetailedAliases();
278+
}
243279
} else {
244280
addToast(result.message, "error");
245281
}
@@ -267,13 +303,22 @@ function App() {
267303
<div className="form-section">
268304
<div className="flex items-center justify-between">
269305
<h2>Settings</h2>
270-
<button
271-
className="btn btn-secondary"
272-
onClick={() => setShowSettings(!showSettings)}
273-
>
274-
<Settings size={16} />
275-
{showSettings ? "Hide" : "Show"} Settings
276-
</button>
306+
<div className="flex gap-2">
307+
<button
308+
className="btn btn-secondary"
309+
onClick={() => setShowAliasViewer(!showAliasViewer)}
310+
>
311+
<List size={16} />
312+
{showAliasViewer ? "Hide" : "View"} Aliases
313+
</button>
314+
<button
315+
className="btn btn-secondary"
316+
onClick={() => setShowSettings(!showSettings)}
317+
>
318+
<Settings size={16} />
319+
{showSettings ? "Hide" : "Show"} Settings
320+
</button>
321+
</div>
277322
</div>
278323

279324
{showSettings && (
@@ -311,6 +356,47 @@ function App() {
311356
)}
312357
</div>
313358

359+
{/* Alias Viewer */}
360+
{showAliasViewer && (
361+
<div className="form-section">
362+
<div className="alias-viewer">
363+
<h3>Existing Git Aliases ({detailedAliases.length})</h3>
364+
{detailedAliases.length === 0 ? (
365+
<p className="no-aliases">No Git aliases found.</p>
366+
) : (
367+
<div className="aliases-grid">
368+
{detailedAliases.map(alias => (
369+
<div key={alias.name} className={`alias-card ${alias.created_by_tool ? 'tool-created' : ''}`}>
370+
<div className="alias-card-header">
371+
<div className="alias-name-section">
372+
<code className="alias-name">{alias.name}</code>
373+
{alias.created_by_tool && (
374+
<span className="tool-badge">Created by this tool</span>
375+
)}
376+
</div>
377+
<button
378+
className="btn-icon btn-danger"
379+
onClick={() => {
380+
if (window.confirm(`Are you sure you want to delete the alias '${alias.name}'?`)) {
381+
deleteAlias(alias.name);
382+
}
383+
}}
384+
title="Delete alias"
385+
>
386+
<Trash2 size={14} />
387+
</button>
388+
</div>
389+
<div className="alias-command">
390+
<code>{alias.command}</code>
391+
</div>
392+
</div>
393+
))}
394+
</div>
395+
)}
396+
</div>
397+
</div>
398+
)}
399+
314400
{/* Alias Name */}
315401
<div className="form-section">
316402
<h2>Alias Name</h2>
@@ -321,6 +407,11 @@ function App() {
321407
onChange={(e) => setAliasName(e.target.value)}
322408
placeholder="Enter alias name (e.g., 'save', 'sync')"
323409
/>
410+
{aliasName.trim() && (
411+
<div className="alias-usage-note">
412+
Run <code>git {aliasName}</code> from the terminal to execute this command after you save it
413+
</div>
414+
)}
324415
{validation && (
325416
<div className={`validation-message ${
326417
validation.valid

0 commit comments

Comments
 (0)