diff --git a/src-tauri/src/commands/history.rs b/src-tauri/src/commands/history.rs index 8cace7a..b28caf2 100644 --- a/src-tauri/src/commands/history.rs +++ b/src-tauri/src/commands/history.rs @@ -3,18 +3,65 @@ use crate::{ services::git_service, }; #[tauri::command] -pub fn get_history(repo_path: String, limit: u32) -> Result, GitError> { +pub fn get_history( + repo_path: String, + limit: u32, + branch: Option, + author: Option, + since: Option, + until: Option, + keyword: Option, + file_path: Option, +) -> Result, GitError> { let fmt = "%H%x1f%h%x1f%P%x1f%an%x1f%ad%x1f%d%x1f%s"; - let out = git_service::git_text( - &repo_path, - &[ - "log", - "--graph", - "--date=short", - &format!("--max-count={limit}"), - &format!("--pretty=format:{fmt}"), - ], - )?; + let max_count = format!("--max-count={limit}"); + let pretty = format!("--pretty=format:{fmt}"); + let author_arg = author + .filter(|v| !v.trim().is_empty() && v != "all") + .map(|v| format!("--author={v}")); + let since_arg = since + .filter(|v| !v.trim().is_empty()) + .map(|v| format!("--since={v}")); + let until_arg = until + .filter(|v| !v.trim().is_empty()) + .map(|v| format!("--until={v}")); + let grep_arg = keyword + .filter(|v| !v.trim().is_empty()) + .map(|v| format!("--grep={v}")); + let branch_arg = branch.filter(|v| !v.trim().is_empty() && v != "all"); + let file_arg = file_path.filter(|v| !v.trim().is_empty()); + + let mut args = vec![ + "log".to_string(), + "--graph".to_string(), + "--decorate".to_string(), + "--date=short".to_string(), + max_count, + pretty, + ]; + if let Some(author) = author_arg { + args.push(author); + } + if let Some(since) = since_arg { + args.push(since); + } + if let Some(until) = until_arg { + args.push(until); + } + if let Some(grep) = grep_arg { + args.push(grep); + } + if let Some(branch) = branch_arg { + args.push(branch); + } else { + args.push("--all".to_string()); + } + if let Some(path) = file_arg { + args.push("--".to_string()); + args.push(path); + } + let refs: Vec<&str> = args.iter().map(String::as_str).collect(); + let out = git_service::git_text(&repo_path, &refs)?; Ok(out .lines() .filter_map(|l| { @@ -72,7 +119,10 @@ pub fn compare_commits(repo_path: String, from: String, to: String) -> Result Result { +pub fn checkout_commit( + repo_path: String, + commit: String, +) -> Result { git_service::git_checked(&repo_path, &["checkout", &commit]) } #[tauri::command] @@ -97,11 +147,17 @@ pub fn create_tag_from_commit( git_service::git_checked(&repo_path, &["tag", &name, &commit]) } #[tauri::command] -pub fn cherry_pick_commit(repo_path: String, commit: String) -> Result { +pub fn cherry_pick_commit( + repo_path: String, + commit: String, +) -> Result { git_service::git_checked(&repo_path, &["cherry-pick", &commit]) } #[tauri::command] -pub fn revert_commit(repo_path: String, commit: String) -> Result { +pub fn revert_commit( + repo_path: String, + commit: String, +) -> Result { git_service::git_checked(&repo_path, &["revert", "--no-edit", &commit]) } #[tauri::command] diff --git a/src/components/graph/GitGraph.tsx b/src/components/graph/GitGraph.tsx index 015c58a..0075355 100644 --- a/src/components/graph/GitGraph.tsx +++ b/src/components/graph/GitGraph.tsx @@ -1,7 +1,7 @@ -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { Filter, GitCommitHorizontal, Search, X } from 'lucide-react'; import { useGitStore } from '../../store/gitStore'; -import type { CommitInfo } from '../../types/git'; +import type { CommitInfo, HistoryFilters } from '../../types/git'; const LANE_COLORS = ['#38bdf8','#a78bfa','#34d399','#fb923c','#f472b6','#facc15','#60a5fa','#4ade80','#e879f9','#f87171']; const ROW_H = 34; @@ -18,41 +18,18 @@ function buildRows(commits: CommitInfo[]): RowData[] { const findLane = (hash: string) => lanes.findIndex(l => l?.hash === hash); const freeLane = () => { const idx = lanes.findIndex(l => l === null); return idx === -1 ? lanes.length : idx; }; const rows: RowData[] = []; - for (const commit of commits) { let myLane = findLane(commit.hash); let myColorIdx: number; - if (myLane === -1) { - myLane = freeLane(); - myColorIdx = nextColor(); - if (myLane === lanes.length) lanes.push(null); - lanes[myLane] = { hash: commit.hash, colorIdx: myColorIdx }; - } else { - myColorIdx = lanes[myLane]!.colorIdx; - } - + if (myLane === -1) { myLane = freeLane(); myColorIdx = nextColor(); if (myLane === lanes.length) lanes.push(null); lanes[myLane] = { hash: commit.hash, colorIdx: myColorIdx }; } + else myColorIdx = lanes[myLane]!.colorIdx; const myColor = LANE_COLORS[myColorIdx]; const lines: RowData['lines'] = []; for (let i = 0; i < lanes.length; i++) if (i !== myLane && lanes[i]) lines.push({ fromLane: i, toLane: i, color: LANE_COLORS[lanes[i]!.colorIdx], pos: 'top' }); - const parents = commit.parents ?? []; lanes[myLane] = null; - if (parents[0]) { - const existingLane = findLane(parents[0]); - if (existingLane === -1) lanes[myLane] = { hash: parents[0], colorIdx: myColorIdx }; - else lines.push({ fromLane: myLane, toLane: existingLane, color: myColor, pos: 'bottom' }); - } - for (let i = 1; i < parents.length; i++) { - const parent = parents[i]; - const existingLane = findLane(parent); - if (existingLane === -1) { - const newLane = freeLane(); - const newColorIdx = nextColor(); - if (newLane === lanes.length) lanes.push(null); - lanes[newLane] = { hash: parent, colorIdx: newColorIdx }; - lines.push({ fromLane: myLane, toLane: newLane, color: LANE_COLORS[newColorIdx], pos: 'bottom' }); - } else lines.push({ fromLane: myLane, toLane: existingLane, color: LANE_COLORS[lanes[existingLane]!.colorIdx], pos: 'bottom' }); - } + if (parents[0]) { const existingLane = findLane(parents[0]); if (existingLane === -1) lanes[myLane] = { hash: parents[0], colorIdx: myColorIdx }; else lines.push({ fromLane: myLane, toLane: existingLane, color: myColor, pos: 'bottom' }); } + for (let i = 1; i < parents.length; i++) { const parent = parents[i]; const existingLane = findLane(parent); if (existingLane === -1) { const newLane = freeLane(); const newColorIdx = nextColor(); if (newLane === lanes.length) lanes.push(null); lanes[newLane] = { hash: parent, colorIdx: newColorIdx }; lines.push({ fromLane: myLane, toLane: newLane, color: LANE_COLORS[newColorIdx], pos: 'bottom' }); } else lines.push({ fromLane: myLane, toLane: existingLane, color: LANE_COLORS[lanes[existingLane]!.colorIdx], pos: 'bottom' }); } for (let i = 0; i < lanes.length; i++) if (i !== myLane && lanes[i]) lines.push({ fromLane: i, toLane: i, color: LANE_COLORS[lanes[i]!.colorIdx], pos: 'bottom' }); rows.push({ commit, lane: myLane, color: myColor, lines, maxLane: Math.max(myLane, ...lanes.map((l, i) => l ? i : 0)) }); } @@ -60,72 +37,54 @@ function buildRows(commits: CommitInfo[]): RowData[] { } const normalized = (value: string) => value.toLowerCase().trim(); - -function matchesCommit(commit: CommitInfo, query: string, author: string, ref: string) { - const q = normalized(query); +function matchesCommit(commit: CommitInfo, query: string) { + const q = normalized(query); if (!q) return true; const refNames = commit.refs.join(' '); - const textMatch = !q || [commit.message, commit.hash, commit.shortHash, commit.author, refNames].some(v => normalized(v).includes(q)); - const authorMatch = author === 'all' || commit.author === author; - const refMatch = ref === 'all' || commit.refs.some(r => r.includes(ref)); - return textMatch && authorMatch && refMatch; + return [commit.message, commit.hash, commit.shortHash, commit.author, refNames].some(v => normalized(v).includes(q)); } -function GraphRow({ row, selected, dimmed, onClick }: { row: RowData; selected: boolean; dimmed: boolean; onClick: () => void }) { +function GraphRow({ row, selected, dimmed, onClick, rowRef }: { row: RowData; selected: boolean; dimmed: boolean; onClick: () => void; rowRef?: (node: HTMLButtonElement | null) => void }) { const svgWidth = (row.maxLane + 1) * LANE_W + PAD_LEFT * 2; - const cy = ROW_H / 2; - const cx = PAD_LEFT + row.lane * LANE_W; - return ; } export function GitGraph() { - const { history, selectedCommit, selectCommit } = useGitStore(s => ({ history: s.history, selectedCommit: s.selectedCommit, selectCommit: s.selectCommit })); - const [query, setQuery] = useState(''); - const [author, setAuthor] = useState('all'); - const [ref, setRef] = useState('all'); + const { history, selectedCommit, selectCommit, historyLimit, historyFilters, loadHistory } = useGitStore(s => ({ history: s.history, selectedCommit: s.selectedCommit, selectCommit: s.selectCommit, historyLimit: s.historyLimit, historyFilters: s.historyFilters, loadHistory: s.loadHistory })); + const [search, setSearch] = useState(''); + const [filters, setFilters] = useState(historyFilters); + const selectedRef = useRef(null); + useEffect(() => { const id = window.setTimeout(() => { void loadHistory(filters, historyLimit); }, 350); return () => window.clearTimeout(id); }, [filters, historyLimit, loadHistory]); + useEffect(() => { selectedRef.current?.scrollIntoView({ block: 'nearest' }); }, [selectedCommit?.hash]); const rows = useMemo(() => buildRows(history), [history]); const authors = useMemo(() => Array.from(new Set(history.map(c => c.author))).sort(), [history]); const refs = useMemo(() => Array.from(new Set(history.flatMap(c => c.refs.map(r => r.replace('HEAD -> ', '').replace('tag: ', ''))))).sort(), [history]); - const visibleRows = rows.filter(row => matchesCommit(row.commit, query, author, ref)); - const hasFilter = Boolean(query || author !== 'all' || ref !== 'all'); - + const visibleRows = rows.filter(row => matchesCommit(row.commit, search)); + const hasFilter = Boolean(search || filters.branch || filters.author || filters.since || filters.until || filters.keyword || filters.filePath); + const clear = () => { setSearch(''); setFilters({}); }; if (history.length === 0) return
Open a repository to see commit history
; - return
-
-
Commit Graph · {visibleRows.length}/{history.length}
- {hasFilter && } -
-
- - - +
Commit Graph · {visibleRows.length}/{history.length}
{hasFilter && }
+
+ + + + setFilters(f => ({ ...f, since: e.target.value || undefined }))} title="Since" /> + setFilters(f => ({ ...f, until: e.target.value || undefined }))} title="Until" />
+
setFilters(f => ({ ...f, keyword: e.target.value || undefined }))} placeholder="Commit message keyword (git --grep)" /> setFilters(f => ({ ...f, filePath: e.target.value || undefined }))} placeholder="File path filter (e.g. src/App.tsx)" />
- {rows.map(row => { - const matched = matchesCommit(row.commit, query, author, ref); - if (hasFilter && !matched) return null; - return void selectCommit(row.commit)} />; - })} + {visibleRows.map(row => { selectedRef.current = node; } : undefined} onClick={() => void selectCommit(row.commit)} />)} {visibleRows.length === 0 &&
No commits match the current GitKraken-style filters.
} +
; } diff --git a/src/services/gitService.ts b/src/services/gitService.ts index b6624a0..69f1720 100644 --- a/src/services/gitService.ts +++ b/src/services/gitService.ts @@ -1,6 +1,6 @@ import { invoke } from '@tauri-apps/api/core'; import { demoHistory, demoRepo, demoStatus } from '../demo/currentRepoDemo'; -import type {AiResponse,BranchInfo,CommitFile,CommitInfo,DiffResult,GitCommandOutput,GitStatus,ParsedConflictFile,RemoteInfo,RepositoryInfo,Settings,StashInfo,TagInfo} from '../types/git'; +import type {AiResponse,BranchInfo,CommitFile,CommitInfo,DiffResult,GitCommandOutput,GitStatus,ParsedConflictFile,RemoteInfo,RepositoryInfo,Settings,StashInfo,TagInfo,HistoryFilters} from '../types/git'; const isTauriRuntime=()=>typeof window!=='undefined'&&'__TAURI_INTERNALS__' in window; const demoSettings={theme:'dark',gitPath:'git',defaultTargetBranch:'main',recentRepositories:[demoRepo.path],aiProvider:'ollama',aiApiKey:'',aiModel:'',validationCommands:[],shortcuts:[]}; @@ -30,7 +30,7 @@ export const gitService={ getDiff:(repoPath:string,filePath:string,cached:boolean)=>call('get_diff',{repoPath,filePath,cached}), getCommitFileDiff:(repoPath:string,commit:string,filePath:string)=>call('get_commit_file_diff',{repoPath,commit,filePath}), commit:(repoPath:string,message:string,amend:boolean)=>call('commit',{repoPath,message,amend}), stagedDiff:(repoPath:string)=>call('staged_diff',{repoPath}), listBranches:(repoPath:string)=>call('list_branches',{repoPath}), createBranch:(repoPath:string,name:string,checkout:boolean)=>call('create_branch',{repoPath,name,checkout}), checkoutBranch:(repoPath:string,name:string)=>call('checkout_branch',{repoPath,name}), renameBranch:(repoPath:string,oldName:string,newName:string)=>call('rename_branch',{repoPath,oldName,newName}), deleteBranch:(repoPath:string,name:string,force:boolean)=>call('delete_branch',{repoPath,name,force}), compareBranch:(repoPath:string,branch:string)=>call('compare_branch',{repoPath,branch}), listRemotes:(repoPath:string)=>call('list_remotes',{repoPath}), fetch:(repoPath:string,remote='origin')=>call('fetch',{repoPath,remote}), pull:(repoPath:string)=>call('pull',{repoPath}), push:(repoPath:string)=>call('push',{repoPath}), pushNewBranch:(repoPath:string,remote:string,branch:string)=>call('push_new_branch',{repoPath,remote,branch}), - getHistory:(repoPath:string,limit=80)=>call('get_history',{repoPath,limit}), getCommitFiles:(repoPath:string,commit:string)=>call('get_commit_files',{repoPath,commit}), compareCommits:(repoPath:string,from:string,to:string)=>call('compare_commits',{repoPath,from,to}), checkoutCommit:(repoPath:string,commit:string)=>call('checkout_commit',{repoPath,commit}), createBranchFromCommit:(repoPath:string,name:string,commit:string,checkout:boolean)=>call('create_branch_from_commit',{repoPath,name,commit,checkout}), createTagFromCommit:(repoPath:string,name:string,commit:string)=>call('create_tag_from_commit',{repoPath,name,commit}), cherryPickCommit:(repoPath:string,commit:string)=>call('cherry_pick_commit',{repoPath,commit}), revertCommit:(repoPath:string,commit:string)=>call('revert_commit',{repoPath,commit}), resetToCommit:(repoPath:string,commit:string,mode:'soft'|'mixed'|'hard')=>call('reset_to_commit',{repoPath,commit,mode}), + getHistory:(repoPath:string,limit=500,filters:HistoryFilters={})=>call('get_history',{repoPath,limit,branch:filters.branch,author:filters.author,since:filters.since,until:filters.until,keyword:filters.keyword,filePath:filters.filePath}), getCommitFiles:(repoPath:string,commit:string)=>call('get_commit_files',{repoPath,commit}), compareCommits:(repoPath:string,from:string,to:string)=>call('compare_commits',{repoPath,from,to}), checkoutCommit:(repoPath:string,commit:string)=>call('checkout_commit',{repoPath,commit}), createBranchFromCommit:(repoPath:string,name:string,commit:string,checkout:boolean)=>call('create_branch_from_commit',{repoPath,name,commit,checkout}), createTagFromCommit:(repoPath:string,name:string,commit:string)=>call('create_tag_from_commit',{repoPath,name,commit}), cherryPickCommit:(repoPath:string,commit:string)=>call('cherry_pick_commit',{repoPath,commit}), revertCommit:(repoPath:string,commit:string)=>call('revert_commit',{repoPath,commit}), resetToCommit:(repoPath:string,commit:string,mode:'soft'|'mixed'|'hard')=>call('reset_to_commit',{repoPath,commit,mode}), mergeBranch:(repoPath:string,branch:string)=>call('merge_branch',{repoPath,branch}), abortMerge:(repoPath:string)=>call('abort_merge',{repoPath}), continueMerge:(repoPath:string)=>call('continue_merge',{repoPath}), parseConflictFile:(repoPath:string,filePath:string)=>call('parse_conflict_file',{repoPath,filePath}), saveResolvedFile:(repoPath:string,filePath:string,content:string)=>call('save_resolved_file',{repoPath,filePath,content}), startRebase:(repoPath:string,onto:string)=>call('start_rebase',{repoPath,onto}), continueRebase:(repoPath:string)=>call('continue_rebase',{repoPath}), abortRebase:(repoPath:string)=>call('abort_rebase',{repoPath}), skipRebase:(repoPath:string)=>call('skip_rebase',{repoPath}), listStashes:(repoPath:string)=>call('list_stashes',{repoPath}), createStash:(repoPath:string,message:string)=>call('create_stash',{repoPath,message}), applyStash:(repoPath:string,stash:string)=>call('apply_stash',{repoPath,stash}), popStash:(repoPath:string,stash:string)=>call('pop_stash',{repoPath,stash}), dropStash:(repoPath:string,stash:string)=>call('drop_stash',{repoPath,stash}), diff --git a/src/store/gitStore.ts b/src/store/gitStore.ts index f3b14a2..220ebc6 100644 --- a/src/store/gitStore.ts +++ b/src/store/gitStore.ts @@ -1,7 +1,7 @@ import { create } from 'zustand'; import { gitService } from '../services/gitService'; -import type {BranchInfo,CommitFile,CommitInfo,DiffResult,GitCommandOutput,GitFileStatus,GitStatus,ParsedConflictFile,RemoteInfo,RepositoryInfo,Settings,StashInfo,TagInfo} from '../types/git'; +import type {BranchInfo,CommitFile,CommitInfo,DiffResult,GitCommandOutput,GitFileStatus,GitStatus,ParsedConflictFile,RemoteInfo,RepositoryInfo,Settings,StashInfo,TagInfo,HistoryFilters} from '../types/git'; const empty:GitStatus={currentBranch:'',staged:[],unstaged:[],untracked:[],conflicted:[],ahead:0,behind:0,mergeState:{isMerging:false,isRebasing:false,conflictedFiles:[]}}; -type State={repo?:RepositoryInfo;recent:string[];status:GitStatus;branches:BranchInfo[];remotes:RemoteInfo[];history:CommitInfo[];commitFiles:CommitFile[];stashes:StashInfo[];tags:TagInfo[];selectedFile?:GitFileStatus;selectedCommit?:CommitInfo;diff?:DiffResult;conflict?:ParsedConflictFile;settings?:Settings;console:string[];problems:string[];aiText:string;busy:boolean;openRepo:(path:string)=>Promise;refresh:()=>Promise;run:(label:string,fn:()=>Promise)=>Promise;setSelectedFile:(f:GitFileStatus,cached:boolean)=>Promise;selectCommit:(c:CommitInfo)=>Promise;loadConflict:(path:string)=>Promise;log:(m:string)=>void;}; +type State={repo?:RepositoryInfo;recent:string[];status:GitStatus;branches:BranchInfo[];remotes:RemoteInfo[];history:CommitInfo[];commitFiles:CommitFile[];stashes:StashInfo[];tags:TagInfo[];selectedFile?:GitFileStatus;selectedCommit?:CommitInfo;diff?:DiffResult;conflict?:ParsedConflictFile;settings?:Settings;console:string[];problems:string[];aiText:string;busy:boolean;historyLimit:number;historyFilters:HistoryFilters;openRepo:(path:string)=>Promise;refresh:()=>Promise;loadHistory:(filters?:HistoryFilters,limit?:number)=>Promise;run:(label:string,fn:()=>Promise)=>Promise;setSelectedFile:(f:GitFileStatus,cached:boolean)=>Promise;selectCommit:(c:CommitInfo)=>Promise;loadConflict:(path:string)=>Promise;log:(m:string)=>void;}; const fmt=(r:unknown):string=>Array.isArray(r)?r.map(fmt).join('\n'):(typeof r==='object'&&r&&'command'in r?`$ ${(r as GitCommandOutput).command}\n${(r as GitCommandOutput).stdout}${(r as GitCommandOutput).stderr}`:String(r)); -export const useGitStore=create((set,get)=>({recent:[],status:empty,branches:[],remotes:[],history:[],commitFiles:[],stashes:[],tags:[],console:[],problems:[],aiText:'',busy:false,log:(m)=>set(s=>({console:[m,...s.console].slice(0,100)})),openRepo:async(path)=>{set({busy:true});try{const repo=await gitService.openRepository(path);await gitService.saveRecentRepository(path);set({repo});await get().refresh()}catch(e){get().log(String((e as Error).message??e))}finally{set({busy:false})}},refresh:async()=>{const repo=get().repo;if(!repo)return;set({busy:true});try{const [status,branches,remotes,history,stashes,tags,recent,settings]=await Promise.all([gitService.getStatus(repo.path),gitService.listBranches(repo.path),gitService.listRemotes(repo.path),gitService.getHistory(repo.path),gitService.listStashes(repo.path),gitService.listTags(repo.path),gitService.listRecentRepositories(),gitService.getSettings()]);set({status,branches,remotes,history,stashes,tags,recent,settings,repo:{...repo,currentBranch:status.currentBranch}})}catch(e){get().log(String((e as Error).message??e))}finally{set({busy:false})}},run:async(label,fn)=>{try{const r=await fn();get().log(`${label}\n${fmt(r)}`);await get().refresh()}catch(e){get().log(String((e as Error).message??e))}},setSelectedFile:async(f,cached)=>{const repo=get().repo;if(!repo)return;set({selectedFile:f,diff:undefined});try{set({diff:await gitService.getDiff(repo.path,f.path,cached)})}catch(e){get().log(String((e as Error).message??e))}},selectCommit:async(c)=>{const repo=get().repo;if(!repo)return;set({selectedCommit:c,commitFiles:await gitService.getCommitFiles(repo.path,c.hash)})},loadConflict:async(path)=>{const repo=get().repo;if(!repo)return;set({conflict:await gitService.parseConflictFile(repo.path,path)})}})); +export const useGitStore=create((set,get)=>({recent:[],status:empty,branches:[],remotes:[],history:[],commitFiles:[],stashes:[],tags:[],console:[],problems:[],aiText:'',busy:false,historyLimit:500,historyFilters:{},log:(m)=>set(s=>({console:[m,...s.console].slice(0,100)})),openRepo:async(path)=>{set({busy:true});try{const repo=await gitService.openRepository(path);await gitService.saveRecentRepository(path);set({repo});await get().refresh()}catch(e){get().log(String((e as Error).message??e))}finally{set({busy:false})}},refresh:async()=>{const repo=get().repo;if(!repo)return;set({busy:true});try{const [status,branches,remotes,history,stashes,tags,recent,settings]=await Promise.all([gitService.getStatus(repo.path),gitService.listBranches(repo.path),gitService.listRemotes(repo.path),gitService.getHistory(repo.path,get().historyLimit,get().historyFilters),gitService.listStashes(repo.path),gitService.listTags(repo.path),gitService.listRecentRepositories(),gitService.getSettings()]);set({status,branches,remotes,history,stashes,tags,recent,settings,repo:{...repo,currentBranch:status.currentBranch}})}catch(e){get().log(String((e as Error).message??e))}finally{set({busy:false})}},loadHistory:async(filters,limit)=>{const repo=get().repo;if(!repo)return;const nextFilters=filters??get().historyFilters;const nextLimit=limit??get().historyLimit;set({busy:true,historyFilters:nextFilters,historyLimit:nextLimit});try{set({history:await gitService.getHistory(repo.path,nextLimit,nextFilters)})}catch(e){get().log(String((e as Error).message??e))}finally{set({busy:false})}},run:async(label,fn)=>{try{const r=await fn();get().log(`${label}\n${fmt(r)}`);await get().refresh()}catch(e){get().log(String((e as Error).message??e))}},setSelectedFile:async(f,cached)=>{const repo=get().repo;if(!repo)return;set({selectedFile:f,diff:undefined});try{set({diff:await gitService.getDiff(repo.path,f.path,cached)})}catch(e){get().log(String((e as Error).message??e))}},selectCommit:async(c)=>{const repo=get().repo;if(!repo)return;set({selectedCommit:c,commitFiles:await gitService.getCommitFiles(repo.path,c.hash)})},loadConflict:async(path)=>{const repo=get().repo;if(!repo)return;set({conflict:await gitService.parseConflictFile(repo.path,path)})}})); diff --git a/src/types/git.ts b/src/types/git.ts index 51ee86a..20d3883 100644 --- a/src/types/git.ts +++ b/src/types/git.ts @@ -8,6 +8,7 @@ export type DiffResult={filePath:string;oldText:string;newText:string;patch:stri export type BranchInfo={name:string;current:boolean;remote:boolean;upstream?:string|null;ahead:number;behind:number}; export type RemoteInfo={name:string;fetchUrl:string;pushUrl:string}; export type CommitInfo={hash:string;shortHash:string;parents:string[];author:string;date:string;message:string;refs:string[];head:boolean;graph:string}; +export type HistoryFilters={branch?:string;author?:string;since?:string;until?:string;keyword?:string;filePath?:string}; export type CommitFile={path:string;status:string}; export type StashInfo={index:number;name:string;branch:string;message:string}; export type TagInfo={name:string;target:string;message:string};