diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f7ccea6..7d9e6ce 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -78,7 +78,8 @@ "Bash(rustfmt --edition 2024 --check src/badge.rs src/main.rs)", "Bash(rustfmt --edition 2024 --check src/git.rs)", "Bash(rustfmt --edition 2024 --check src/badge.rs)", - "Bash(rustfmt --edition 2024 --check src/git.rs src/main.rs)" + "Bash(rustfmt --edition 2024 --check src/git.rs src/main.rs)", + "Bash(pkg-config --list-all)" ] } } diff --git a/src-tauri/src/git.rs b/src-tauri/src/git.rs index 7d25b81..a3a67c8 100644 --- a/src-tauri/src/git.rs +++ b/src-tauri/src/git.rs @@ -3415,16 +3415,47 @@ fn is_auth_error(details: &str) -> bool { || d.contains("authentication required") } +// Windows' CreateProcess rejects command lines longer than ~32K chars with +// "os error 206" (filename or extension too long). Staging/restoring a large +// number of files can easily exceed that, so split the paths across multiple +// invocations and concatenate their output. +const MAX_PATH_ARGS_CHARS: usize = 8_000; + fn run_git_with_paths( repo: &Path, base_args: &[&str], files: &[String], ) -> Result, String> { - let mut args = Vec::with_capacity(base_args.len() + files.len() + 1); - args.extend(base_args.iter().map(OsString::from)); - args.push(OsString::from("--")); - args.extend(files.iter().map(OsString::from)); - run_git(repo, args) + if files.is_empty() { + let args: Vec = base_args.iter().map(OsString::from).collect(); + return run_git(repo, args); + } + + let mut combined = Vec::new(); + let mut start = 0; + while start < files.len() { + let mut end = start; + let mut chunk_chars = 0usize; + while end < files.len() { + let len = files[end].len() + 1; + if end > start && chunk_chars + len > MAX_PATH_ARGS_CHARS { + break; + } + chunk_chars += len; + end += 1; + } + let chunk = &files[start..end]; + + let mut args = Vec::with_capacity(base_args.len() + chunk.len() + 1); + args.extend(base_args.iter().map(OsString::from)); + args.push(OsString::from("--")); + args.extend(chunk.iter().map(OsString::from)); + combined.extend(run_git(repo, args)?); + + start = end; + } + + Ok(combined) } fn run_git_with_paths_cancellable( diff --git a/src/app.css b/src/app.css index 1d1be11..6dac7bd 100644 --- a/src/app.css +++ b/src/app.css @@ -1639,6 +1639,7 @@ .branch-actions .btn-sm { min-height: 24px; padding: 0 6px; font-size: 11px; } .branch-context-menu, + .history-context-menu, .explorer-context-menu { z-index: 120; display: grid; @@ -1651,10 +1652,12 @@ box-shadow: 0 18px 50px rgba(0,0,0,0.5); } - .branch-context-menu { position: absolute; } + .branch-context-menu, + .history-context-menu { position: absolute; } .explorer-context-menu { position: fixed; } .branch-context-menu button, + .history-context-menu button, .explorer-context-menu button { display: flex; align-items: center; @@ -1673,13 +1676,15 @@ } .branch-context-menu button:hover:not(:disabled), + .history-context-menu button:hover:not(:disabled), .explorer-context-menu button:hover:not(:disabled) { border-color: var(--color-border-subtle); background: rgba(255,255,255,0.06); color: var(--color-ink); } - .branch-context-menu .menu-separator { + .branch-context-menu .menu-separator, + .history-context-menu .menu-separator { height: 1px; margin: 4px 3px; background: var(--color-border-subtle); @@ -1696,6 +1701,7 @@ } .branch-context-menu button:disabled, + .history-context-menu button:disabled, .explorer-context-menu button:disabled { cursor: not-allowed; opacity: 0.48; @@ -1817,6 +1823,7 @@ /* --- Commit history --- */ + .history-panel { position: relative; } .history-list { min-width: 0; padding: 6px; overflow: auto; } .commit-row { display: grid; min-width: 0; gap: 8px; padding: 10px; border: 1px solid var(--color-border-subtle); border-radius: 8px; background: var(--color-surface-raised); transition: border-color 120ms; } @@ -2017,6 +2024,22 @@ } .commit-action-buttons { display: flex; flex: 0 0 auto; align-items: center; gap: 5px; } .commit-action-buttons button { flex: 0 0 auto; white-space: nowrap; } + .commit-menu-button { + width: 28px; + min-width: 28px; + min-height: 26px; + padding: 0; + border-color: rgba(94,110,156,0.16); + border-radius: 7px; + background: rgba(255,255,255,0.035); + color: var(--color-ink-dim); + } + .commit-menu-button:hover:not(:disabled), + .commit-menu-button[aria-expanded="true"] { + border-color: rgba(65,209,255,0.28); + background: rgba(65,209,255,0.08); + color: var(--color-ink); + } .file-history-head { align-items: flex-start; } .file-history-heading { min-width: 0; flex: 1 1 auto; overflow: hidden; } .section-head .file-history-name { diff --git a/src/lib/components/HistoryPanel.svelte b/src/lib/components/HistoryPanel.svelte index 4e56f2b..49f9885 100644 --- a/src/lib/components/HistoryPanel.svelte +++ b/src/lib/components/HistoryPanel.svelte @@ -1,5 +1,5 @@ - + -
+
History @@ -431,9 +501,14 @@ {#each visibleCommitEntries as entry, rowIndex (entry.commit.hash)} {@const item = entry.commit} {@const row = graphRows[rowIndex]} - {@const hoverBranchRefs = visibleBranchLabels(row?.branchLabels ?? [])} + {@const hoverBranchRefs = commitHoverBranchLabels(item, row)} {@const otherRefs = visibleRefs(item)} -
1} class:root-row={item.parents.length === 0} class:tip-row={item.refs.length > 0}> +
1} + class:root-row={item.parents.length === 0} + class:tip-row={item.refs.length > 0} + > {/if} + + {#if contextCommit} + + {/if}
{#if branchDialogOpen}