feat(git): update --include-logs to optionally pack git commit history with graphs, metadata, and diffs/patches - #968
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR adds comprehensive Git commit history analysis capabilities to Repomix, introducing CLI flags (--patch, --stat, --graph, --summary, --commit-range) and configuration options to retrieve, parse, and render structured commit metadata, patches, ASCII/Mermaid graphs, and tags. The implementation spans CLI parsing, config schema, core git parsing utilities, output generation, and documentation. Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI<br/>(cliRun.ts)
participant Action as Action<br/>(defaultAction.ts)
participant GitCmd as Git Commands<br/>(gitCommand.ts)
participant GitHist as Git History<br/>(gitHistory.ts)
participant GitLog as Git Log Handle<br/>(gitLogHandle.ts)
participant Output as Output Generate<br/>(outputGenerate.ts)
participant Render as Render Styles<br/>(markdown/xml/json)
CLI->>Action: Parse flags (--patch, --graph, --summary, --commit-range)
Action->>Action: Validate mutual exclusion, derive commitPatchDetail
Action->>Action: Augment cliConfig with git options
Action->>GitLog: getGitLogs(config, deps)
GitLog->>GitCmd: execGitLogStructured(range, maxCommits)
GitCmd-->>GitLog: NUL-delimited structured metadata
GitLog->>GitHist: parseCommitRange(range)
GitHist-->>GitLog: { from, to, raw }
alt includeCommitPatches
GitLog->>GitCmd: execGitLogTextBlob(range, patchDetail)
GitCmd-->>GitLog: patch/stat/diff text blobs
end
alt includeCommitGraph
GitLog->>GitCmd: execGitGraph(range)
GitCmd-->>GitLog: git log --graph output
GitLog->>GitHist: generateMermaidGraph(commits, tags)
GitHist-->>GitLog: Mermaid diagram string
end
GitLog->>GitHist: getTags(directory)
GitHist-->>GitLog: tag→hash map
GitLog-->>Action: GitLogResult { logCommits[], graph?, summary? }
Action->>Output: generateOutput(context, gitLogResult)
Output->>Output: Build RenderContext with gitCommitHistorySummary, gitCommitGraph
Output->>Render: Render git history section
Render->>Render: Format commits, patches, graph, tags per style
Render-->>Output: Rendered markdown/XML/JSON
Output-->>Action: Final output string
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Areas requiring extra attention:
Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip You can disable sequence diagrams in the walkthrough.Disable the |
Summary of ChangesHello @ahundt, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
|
Hey, I addressed the ai-generated review feedback in the latest commit: Fixed:
Kept as-is:
Out of scope:
894 tests passing, lint clean. Let me know if you'd like any changes! Thanks for the useful tool! |
dfda264 to
67ff18a
Compare
e2b824d to
3b8412a
Compare
|
@yamadashy this seems to be in reasonably good shape now if you're up for giving it a look! The summary at the very top should be up to date now. /gemini review @coderabbitai full review |
|
@ahundt, thank you for the comprehensive updates and for addressing the previous feedback! I can see you've made thoughtful improvements: ✅ Robust parsing with null byte delimiters Your decisions on keeping I'll perform a full review of the entire PR now to provide comprehensive feedback on the implementation. ✅ Actions performedFull review triggered. |
|
Hello @ahundt, Thank you for the detailed update and for addressing the feedback from the previous review. I appreciate the thoroughness of your changes and the clear explanations for the decisions made. I've reviewed the latest commit, and the fixes you've implemented are excellent:
Your decision to keep the The new two-pass architecture for Git log parsing, along with the dedicated modules for Git operations ( Overall, this is a very well-executed enhancement. Great work! I will now proceed with a full review as requested by |
3b8412a to
e0ff030
Compare
|
Hi @ahundt! I've been thinking about extending the git history-related features, so the direction looks good to me. I'd like to take some time to think about the option design. I don't have time to review the code in detail right now, but I'll take a closer look later! |
|
Ok I stuck to your existing pattern and it is essentially a pass through of the existing git commands which I view as one of the the likeliest paths users and tools like ais will be able to run with existing knowledge, and thus a reasonable choice. It is dramatically simplified from the initial commit. |
…ta extraction Summary: Enable detailed git commit history in repository output for AI systems to understand code evolution, including ASCII/Mermaid graph visualization, commit metadata extraction (hash/author/committer/parents/files), and configurable patch detail levels. Previous behavior: Repomix supported basic git log output via --include-logs flag, which only displayed simple chronological commit messages without graph topology, detailed metadata (author/committer/parents), diff patches, or branch/merge visualization. What changed: - src/cli/cliRun.ts: add 6 CLI options (--include-commit-history, --commit-range, --commit-patch-detail, --no-commit-graph, --no-git-tags, --no-commit-patches) with semantic suggestion mappings - src/cli/actions/defaultAction.ts: add buildCliConfig() logic to process commit history options and merge into config.output.git - src/cli/types.ts: add CliOptions fields for commit history (includeCommitHistory, commitRange, commitPatchDetail, commitGraph, gitTags, commitPatches) - src/config/configSchema.ts: add repomixConfigBaseSchema and repomixConfigDefaultSchema fields for commit history config with defaults (commitRange: 'HEAD~50..HEAD', commitPatchDetail: 'stat', includeCommitGraph: true, includeGitTags: true, includeCommitPatches: true) - src/core/git/gitHistory.ts: create parseCommitRange(), getCommitMetadata(), getCommitGraph(), getCommitPatch(), getTags() functions to execute git log/show commands, parse commit data including full author/committer metadata, generate ASCII/Mermaid graphs from topology, and retrieve diffs at configurable detail levels - src/core/git/gitHistoryHandle.ts: create getGitHistory() to orchestrate git history collection with configurable patch detail levels (full/stat/files/metadata) - src/core/output/outputGenerate.ts: integrate getGitHistory() and add gitCommitHistory* fields to template data - src/core/output/outputGeneratorTypes.ts: add GeneratedOutputData fields for gitCommitHistory, gitCommitGraph, gitCommitHistoryItems - src/core/output/outputStyles/markdownStyle.ts: add Git Commit History section with Summary (total commits/merge commits/range/detail level), Commit Graph (ASCII/Mermaid/Tags), and per-commit details (hash/author/committer/date/message/body/files/patch) - src/core/output/outputStyles/xmlStyle.ts: add <git_history> with <summary>, <commit_graph>, and <commit> elements including full author/committer metadata - src/core/packager.ts: add gitHistory parameter to packDirectory() - tests/core/git/gitHistory.test.ts: add tests for parseCommitRange, getCommitMetadata, getCommitGraph, getCommitPatch, getTags functions (19 tests, 282 lines) - tests/*: add integration tests and update existing tests for git history functionality - website/client/src/en/guide/tips/git-commit-history.md: create guide explaining feature usage, patch detail levels, and configuration options - website/client/src/en/guide/*.md: update command-line-options.md, configuration.md, output.md, usage.md with git commit history documentation - website/client/.vitepress/config/configEnUs.ts: add sidebar link to git-commit-history guide - README.md: add git commit history feature to main feature list with examples - llms-install.md: document includeCommitHistory, commitRange, commitPatchDetail options for MCP tools Why: AI systems analyzing repositories need to understand code evolution patterns beyond individual file contents. Commit history reveals development patterns, identifies code stability risks, and provides context about why changes were made. Graph visualization shows branch/merge topology for understanding parallel development. Raw author/committer email metadata allows consumers to identify development patterns (e.g., commits from AI assistants like claude@ or copilot@ vs human developers) without pre-classifying commits. This enables more context-aware AI analysis of repository health and development practices. Files affected: - 2 new core files: src/core/git/gitHistory.ts (372 lines), src/core/git/gitHistoryHandle.ts (149 lines) - 8 modified source files: CLI (cliRun.ts, defaultAction.ts, types.ts), config (configSchema.ts), output (outputGenerate.ts, outputGeneratorTypes.ts, markdownStyle.ts, xmlStyle.ts) - 1 modified packager: src/core/packager.ts - 10 test files added/modified: comprehensive unit tests for all git history functions (871 tests pass) - 5 documentation files: new guide + updates to existing guides - 2 project files: README.md, llms-install.md Testable: ```bash # Basic commit history with graph npx repomix --include-commit-history --output output.xml # Analyze last 20 commits npx repomix --include-commit-history --commit-range HEAD~20..HEAD # Full diffs for specific range npx repomix --include-commit-history --commit-range v1.0..v2.0 --commit-patch-detail full # Metadata only (no patches, no graph) npx repomix --include-commit-history --commit-patch-detail metadata --no-commit-graph # Verify author/committer metadata is visible npx repomix --include-commit-history -o output.xml grep '<email>' output.xml # Run tests npm run test # 871 tests pass npm run lint # 0 errors ```
…syntax, tests
Summary: Fix commit history parsing by replacing fragile body/file heuristic with
null byte delimiter, add missing git_history section to parsable XML output, fix
invalid Mermaid gitGraph syntax, add files list to XML/Markdown templates.
Previous behavior:
- getCommitMetadata() in gitHistory.ts used heuristic loop assuming body lines
start with spaces or are empty, which misclassified body text like
"This line has no leading space" as a file path
- generateMermaidGraph() produced invalid syntax: `merge abc1234 tag: "message"`
and separate `commit tag: "v1.0"` lines which Mermaid parsers reject
- generateParsableXmlOutput() in outputGenerate.ts had no git_history section,
while generateParsableJsonOutput() had complete gitCommitHistory structure
- xmlStyle.ts and markdownStyle.ts templates rendered commits without files list
even though metadata.files was available
What changed:
- src/core/git/gitHistory.ts:
- Add NULL_BYTE constant and %x00 to git log format string after %b
- Split stdout on NULL_BYTE: `const [formatOutput, fileListOutput] = result.stdout.split(NULL_BYTE)`
- Body parsing: `lines.slice(10).join('\n').trim()` instead of heuristic loop
- Files parsing: `fileListOutput.split('\n').filter(Boolean)` instead of heuristic
- Add escapeMermaidString(): replaces " with ' and \n with space
- Fix Mermaid syntax: `commit id: "abc1234: (merge) message" tag: "v1.0" type: HIGHLIGHT`
instead of separate merge/tag lines
- src/core/output/outputGenerate.ts:
- Add git_history block to generateParsableXmlOutput() matching JSON structure:
summary (total_commits, merge_commits, range, detail_level),
commit_graph (ascii_graph, mermaid_graph, merge_commits, tags),
commits array with @_hash/@_abbreviated_hash attributes, author/committer/parents/message/body/files/patch
- src/core/output/outputStyles/xmlStyle.ts:
- Add `{{#if this.metadata.files.length}}<files>{{#each}}<file>{{{this}}}</file>{{/each}}</files>{{/if}}`
- src/core/output/outputStyles/markdownStyle.ts:
- Add `{{#if this.metadata.files.length}}**Files Changed**:\n{{#each}}- \`{{{this}}}\`{{/each}}{{/if}}`
- tests/core/git/gitHistory.test.ts:
- Add NULL_BYTE constant matching implementation
- Update existing tests to use NULL_BYTE instead of empty line after body
- Add test: body text without leading spaces parsed correctly (edge case fix verification)
- Add test: empty body with NULL_BYTE handled
- Add test: files with special characters (parentheses, brackets, hyphens) in paths
- Add test: commit with no files changed returns empty array
- tests/core/git/gitHistoryHandle.test.ts (new file, 264 lines):
- Test: returns undefined when includeCommitHistory is false
- Test: returns undefined when directory is not a git repository
- Test: returns git history with default options (range HEAD~50..HEAD, detailLevel stat)
- Test: uses custom commitRange from config
- Test: uses custom commitPatchDetail from config
- Test: excludes graph when includeCommitGraph is false
- Test: excludes tags when includeGitTags is false (returns empty object)
- Test: excludes patches when includeCommitPatches is false
- Test: counts merge commits correctly (commits with 2+ parents)
- Test: throws RepomixError on git command failure
- Test: uses cwd when rootDirs is empty
- tests/core/output/outputGenerate.test.ts:
- Add test: markdown output includes Files Changed section with bullet list
- Add test: parsable XML output includes git_history with summary/commit_graph/commits
- Add test: XML template output includes <files>/<file> elements
- Add test: parsable XML commit_graph includes merge_commits field
- website/client/src/en/guide/configuration.md:
- Add includeCommitPatches option to table: "Whether to include code patches (diffs) for each commit"
- Add complete example config JSON with all commit history options
- Add "Commit History Analysis" section explaining each option
- website/client/src/en/guide/output.md:
- Update XML example: hash="abc123def456789" instead of "abc123"
- Add <parents><parent>def456abc789012</parent></parents> element
- Add <body>Extended commit message body...</body> element
- Add <files><file>src/feature.ts</file><file>tests/feature.test.ts</file></files> element
- website/client/src/en/guide/tips/git-commit-history.md:
- Clarify --all flag: "shows how branches connect within the specified range"
Why: Null bytes (\0) cannot appear in git commit messages or file paths, making
%x00 a reliable delimiter vs heuristics that fail on body text without indentation.
Mermaid gitGraph requires specific syntax (commit id: "..." tag: "..." type: ...).
XML/JSON output parity ensures consistent data regardless of format choice.
Testable: npm run test (894 pass), npm run lint (pass).
Generate: repomix --include-commit-history --style xml --parsable-style
Verify: XML contains <git_history> with <summary>, <commit_graph>, <commits>
…ional parameter Previous behavior: - CLI had 6 separate flags: --include-commit-history, --commit-range, --commit-patch-detail, --no-commit-graph, --no-git-tags, --no-commit-patches - Patch detail levels used git-incompatible names: 'full', 'files' - Documentation used vague terms like "complete diffs", "change statistics" - Config file and CLI both exposed all fine-grained controls What changed: - Simplified CLI to 2 flags with optional parameter syntax - Renamed patch levels to match git log parameters: 'full'→'patch', 'files'→'name-only' - Made patch detail level an optional parameter: --include-commit-history [level] - Config file retains all advanced options (includeCommitGraph, includeGitTags, includeCommitPatches) - Updated all descriptions to use concrete terms with examples - Added git parameter references throughout documentation Why: This change follows the principle of optimizing for the 95% use case. Most users want simple commit history analysis without fine-tuning every component. The simplified CLI makes common tasks easier (just --include-commit-history patch) while advanced users can still use config files for edge cases. Parameter names now match git's conventions for familiarity. Files changed: - src/cli/cliRun.ts: Simplified from 6 flags to 2, added optional parameter with validation - src/cli/types.ts: Changed includeCommitHistory to boolean | string type, removed 3 boolean flags - src/cli/actions/defaultAction.ts: Streamlined config building logic, handle string vs boolean type - src/config/configSchema.ts: Updated enum from ['full', 'stat', 'files', 'metadata'] to ['patch', 'stat', 'name-only', 'metadata'] - src/core/git/gitHistory.ts: Updated PatchDetailLevel type and switch cases for 'patch' and 'name-only' - tests/core/git/gitHistory.test.ts: Updated test cases to use new parameter names - tests/core/git/gitHistoryHandle.test.ts: Changed 'full' to 'patch' in test expectations - tests/core/output/outputGenerate.test.ts: Updated detailLevel from 'full' to 'patch' - README.md: Replaced vague terms with concrete examples, simplified CLI examples - llms-install.md: Added git log parameter references to commitPatchDetail descriptions - website/client/src/en/guide/command-line-options.md: Condensed 6 options into 2 with git param mapping - website/client/src/en/guide/configuration.md: Updated table and detailed explanations with concrete terms - website/client/src/en/guide/tips/git-commit-history.md: Added Git Param column to table, made examples concise - website/client/src/en/guide/output.md: Added git param references to patch detail levels - website/client/src/en/guide/usage.md: Updated examples to use simplified syntax with git params Technical details: - Commander.js optional parameter syntax: --include-commit-history [level] - Validation function checks valid levels: ['patch', 'stat', 'name-only', 'metadata'] - Type narrowing in defaultAction.ts: typeof options.includeCommitHistory === 'string' - Zod enum validation updated in both base and default schemas - Git commands mapped: patch→--patch, stat→--stat, name-only→--name-only, metadata→--no-patch Testable: - Run: npm run lint (passes: 0 errors, 0 warnings) - Run: npm test (passes: 894/894 tests) - Run: repomix --include-commit-history (uses stat level by default) - Run: repomix --include-commit-history patch (shows line-by-line diffs) - Run: repomix --include-commit-history name-only (shows filenames only) - Run: repomix --include-commit-history metadata (no diffs, metadata only) - Run: repomix --include-commit-history invalid (shows error with valid options)
…git log Replace --include-commit-history with orthogonal CLI flags that directly map to git log parameters, enabling precise control over diff output format and enhancement features. Previous behavior: - Single --include-commit-history flag enabled comprehensive history - gitHistoryHandle.ts separate from gitLogHandle.ts - includeCommitHistory config field controlled all history features - Limited to 4 diff formats (patch, stat, name-only, name-status) - No separate control over graph visualization vs summary output What changed: - Removed --include-commit-history CLI flag - Removed includeCommitHistory config field - Deleted src/core/git/gitHistoryHandle.ts (merged into gitLogHandle.ts) - Added 8 diff format flags matching git log: --stat, --patch, --numstat, --shortstat, --dirstat, --name-only, --name-status, --raw - Added 2 enhancement flags: --graph (commit graph visualization), --summary (file operations like creates, renames, mode changes) - Extended commitPatchDetail enum to support all 8 formats - Added includeSummary config field for granular control - Implemented mutual exclusion validation for diff format flags - Unified getGitLogs() function returns GitLogResult | GitHistoryResult - Automatic routing between simple mode (includeLogs only) and comprehensive mode (includeCommitGraph or includeSummary enabled) - Support both .. and ... commit range syntax in all documentation Why: Enable users to select exact git log output format needed for their use case without enabling unwanted features. Orthogonal flags match git log's design, making the interface intuitive for git users. Consolidating handlers reduces code duplication and maintenance burden. Files affected: Core implementation: - src/config/configSchema.ts: extended commitPatchDetail enum to 8 formats, added includeSummary field, removed includeCommitHistory - src/core/git/gitHistory.ts: extended PatchDetailLevel type with 5 new formats (numstat, shortstat, dirstat, name-status, raw), added includeSummary parameter to getCommitPatch() - src/core/git/gitLogHandle.ts: merged all gitHistoryHandle.ts functionality, added getComprehensiveGitHistory() and getSimpleGitLogs() helpers, unified getGitLogs() with routing logic - src/core/git/gitHistoryHandle.ts: deleted (consolidated into gitLogHandle.ts) - src/core/output/outputGenerate.ts: removed gitHistoryResult parameter from generateOutput() and buildOutputGeneratorContext() (8→6 params, 7→6 params), updated createRenderContext() to detect and extract from GitLogResult | GitHistoryResult union type - src/core/packager.ts: replaced separate getGitHistory() call with single unified getGitLogs() call CLI: - src/cli/types.ts: removed includeCommitHistory field, added 8 orthogonal diff format flags (stat, patch, numstat, shortstat, dirstat, nameOnly, nameStatus, raw) and 2 enhancement flags (graph, summary) - src/cli/cliRun.ts: removed --include-commit-history option, added 8 diff format option flags and 2 enhancement option flags with descriptions - src/cli/actions/defaultAction.ts: implemented mutual exclusion validation for diff format flags (lines 287-302), mapped each CLI flag to appropriate config field (lines 305-340), fixed linter error by removing duplicate else-if block Tests (fixed compilation errors): - tests/core/output/diffsInOutput.test.ts: updated generateOutput() calls from 8 to 6 parameters - tests/core/output/flagFullDirectoryStructure.test.ts: removed includeCommitHistory, added includeSummary, updated buildOutputGeneratorContext() calls from 7 to 6 parameters - tests/core/output/outputGenerate.test.ts: replaced includeCommitHistory with includeCommitGraph + includeCommitPatches in mock structures - tests/core/output/outputGenerateDiffs.test.ts: updated generateOutput() calls from 8 to 6 parameters - tests/core/output/outputStyles/jsonStyle.test.ts: removed includeCommitHistory, added includeSummary - tests/core/metrics/calculateGitDiffMetrics.test.ts: removed includeCommitHistory, added includeSummary - tests/core/metrics/calculateGitLogMetrics.test.ts: removed includeCommitHistory, added includeSummary - tests/core/git/gitLogHandle.test.ts: added missing mock dependencies (isGitRepository, getCommitGraph, getCommitPatch), added type casts for union type access Documentation (with concrete examples for both .. and ... syntax): - README.md: replaced --include-commit-history examples with orthogonal flags, listed all 8 diff format flags and 2 enhancement flags - website/client/src/en/guide/tips/git-commit-history.md: complete rewrite with orthogonal parameter design, detailed tables for diff format and enhancement flags, workflow examples - website/client/src/en/guide/usage.md: updated Commit History Analysis section with all flags and examples - website/client/src/en/guide/command-line-options.md: reorganized Git Commit History Options section with detailed flag descriptions - website/client/src/en/guide/output.md: updated to reflect new flag structure and all diff formats Testable: - Verify mutual exclusion: npx repomix --stat --patch (should error) - Test diff format flag: npx repomix --name-only --commit-range HEAD~10..HEAD - Test enhancement flags: npx repomix --name-status --graph --summary - Test both range syntaxes: npx repomix --stat --commit-range main..feature vs npx repomix --stat --commit-range main...feature - Verify simple mode: npx repomix --include-logs (no graph/summary) - Verify comprehensive mode: npx repomix --include-logs --graph
…t codebase Update all functions that accept git log results to handle the new union type (GitLogResult | GitHistoryResult) returned by the unified getGitLogs() function. Previous behavior: - Functions expected only GitLogResult type - GitHistoryResult couldn't be passed to security checks or metrics - Test mocks used old includeCommitHistory field - Test mocks called functions with wrong parameter counts - Boolean expression returned non-boolean type What changed: Type signature updates: - src/core/metrics/calculateMetrics.ts: updated gitLogResult parameter type from GitLogResult | undefined to GitLogResult | GitHistoryResult | undefined - src/core/metrics/calculateGitLogMetrics.ts: same type update, added logic to extract content from either logContent (simple) or graph.graph (comprehensive) - src/core/security/validateFileSafety.ts: same type update for gitLogResult parameter - src/core/security/securityCheck.ts: same type update, added logic to check both logContent and graph.graph fields for security scanning Union type handling: - src/core/output/outputGenerate.ts:36: added !! boolean coercion to fix "boolean | undefined" assignment error - src/core/metrics/calculateGitLogMetrics.ts:24-30: added type discrimination logic using 'logContent' in gitLogResult vs 'graph' in gitLogResult - src/core/security/securityCheck.ts:50-64: same discrimination logic for security checks Test fixes: - tests/config/configSchema.test.ts: replaced includeCommitHistory with includeSummary (2 occurrences) - tests/core/packager.test.ts:90-97: updated generateOutput() mock call from 7 to 6 parameters (removed extra undefined) - tests/core/git/gitHistory.test.ts:314,334,354: added includeSummary parameter (false) to getCommitPatch() calls, replaced "metadata" test with "includeSummary" test - tests/core/git/gitHistoryHandle.test.ts: deleted file (consolidation into gitLogHandle.ts completed) Why: The consolidated getGitLogs() function returns a union type to support both simple logs and comprehensive history. All consuming functions must handle both types correctly to avoid TypeScript compilation errors and runtime failures. Tests needed updates to match new function signatures and remove references to deleted functionality. Files affected: - src/core/metrics/calculateGitLogMetrics.ts: type update, union discrimination - src/core/metrics/calculateMetrics.ts: type signature update - src/core/output/outputGenerate.ts: boolean coercion fix - src/core/security/securityCheck.ts: type update, union discrimination - src/core/security/validateFileSafety.ts: type signature update - tests/config/configSchema.test.ts: replaced includeCommitHistory - tests/core/packager.test.ts: fixed parameter count - tests/core/git/gitHistory.test.ts: added includeSummary parameter, updated test - tests/core/git/gitHistoryHandle.test.ts: deleted obsolete file Testable: - npm run build (passes without TypeScript errors) - npm test (all 883 tests pass across 97 test files) - Verify union type handling: Git operations work with both simple and comprehensive modes - Verify security checks: Both logContent and graph fields scanned - Verify metrics: Token counts calculated for both result types
…ult into GitLogResult eliminating union type
Summary: Replace union type (GitLogResult | GitHistoryResult) with single GitLogResult interface using optional fields for simple/comprehensive modes, and add conditional checks to prevent git_logs/gitLogs output when comprehensive mode is active.
Previous behavior (from git diff --staged):
- getGitLogs() in gitLogHandle.ts:300 returned GitLogResult | GitHistoryResult union type
- createRenderContext() in outputGenerate.ts:33 used 'summary' in result checks for type discrimination
- generateParsableXmlOutput() line 119: git_logs block output when renderContext.gitLogEnabled (no check for comprehensive mode)
- generateParsableJsonOutput() line 210: gitLogs field output when renderContext.gitLogEnabled (no check for comprehensive mode)
- Result: empty <git_logs></git_logs> tag appeared alongside <git_history> in XML
- Result: gitLogs: undefined appeared alongside gitCommitHistory in JSON
- Tests expected old {date, message, files} structure instead of {metadata: {...}, patch} structure
What changed:
Type consolidation (src/core/git/gitLogHandle.ts):
- Line 56: Renamed GitHistoryResult interface to GitLogResult with optional fields
- Line 27-29: Made patch field optional in HistoryCommitResult (patch?: string)
- Line 56-66: GitLogResult structure: commits (required), graph/summary/logContent (optional)
- Lines 156-176: getSimpleGitLogs() converts {date, message, files} to {metadata: CommitMetadata, patch: undefined}
- Line 199: getComprehensiveGitHistory() return type changed from GitHistoryResult to GitLogResult
- Line 300: getGitLogs() return type changed from union to single GitLogResult | undefined
Conditional output logic (src/core/output/outputGenerate.ts):
- Line 8: Removed GitHistoryResult import, kept GitLogCommit and GitLogResult
- Lines 36-50: Added extractSimpleCommits() function to convert HistoryCommitResult[] to GitLogCommit[] for templates
- Line 53: Changed isComprehensiveHistory from config check to data check: !!(gitLogResult?.graph || gitLogResult?.summary)
- Lines 77-82: Access gitLogResult fields directly instead of separate gitHistoryResult parameter
- Line 119: Added && !renderContext.gitCommitHistoryEnabled to git_logs condition (prevents dual output in XML)
- Line 210: Added && !renderContext.gitCommitHistoryEnabled to gitLogs condition (prevents dual output in JSON)
Type signature updates (removed union type):
- src/core/output/outputGeneratorTypes.ts:13: gitLogResult from GitLogResult | GitHistoryResult | undefined to GitLogResult | undefined
- src/core/metrics/calculateMetrics.ts:29: gitLogResult parameter type
- src/core/metrics/calculateGitLogMetrics.ts:8-26: gitLogResult parameter, extract content from logContent || graph?.graph
- src/core/security/securityCheck.ts:10: gitLogResult parameter type
- src/core/security/validateFileSafety.ts:17: gitLogResult parameter type
Test structure updates:
- tests/core/git/gitLogHandle.test.ts:89-134: Changed 3 test expectations from {date, message, files} to {metadata: {hash, abbreviatedHash, parents, author: {name, email, date}, committer: {name, email, date}, message, body, files}, patch: undefined}
- tests/core/git/gitLogHandle.test.ts:3-9: Added import type GitLogResult on separate line (TypeScript best practice)
- tests/core/git/gitLogHandle.test.ts:84-239: Added missing deps (isGitRepository, getCommitGraph, getCommitPatch) to all getGitLogs test calls
- tests/core/output/outputGenerate.test.ts:67-92: Updated mock gitLogResult to use metadata wrapper structure
- tests/cli/actions/workers/defaultActionWorker.test.ts:62: Removed includeCommitHistory field (no longer exists)
- tests/cli/actions/workers/defaultActionWorker.test.ts:65: Added includeSummary field (required by schema)
- tests/core/git/gitHistory.test.ts:392: Added missing includeSummary parameter to getCommitPatch call
Why: Union types require type discrimination checks ('summary' in result) scattered across 9 files. Single interface with optional fields:
1. Removes all type discrimination logic from outputGenerate.ts, calculateGitLogMetrics.ts
2. Single function signature GitLogResult | undefined instead of GitLogResult | GitHistoryResult | undefined
3. Backward compatibility via optional logContent field for simple mode
4. Fixes XML/JSON template substitution bug where git_logs appeared empty alongside git_history
5. Data-driven mode detection (graph || summary present) instead of config flag check
Files affected:
- src/core/git/gitLogHandle.ts (102 insertions): Type consolidation, metadata wrapper conversion
- src/core/output/outputGenerate.ts (50 modifications): Removed type discrimination, added dual-output prevention
- src/core/output/outputGeneratorTypes.ts (10 modifications): Removed union type from interface
- src/core/metrics/calculateGitLogMetrics.ts (15 modifications): Extract content from unified type
- src/core/metrics/calculateMetrics.ts (4 modifications): Updated parameter type
- src/core/security/securityCheck.ts (8 modifications): Updated parameter type
- src/core/security/validateFileSafety.ts (4 modifications): Updated parameter type
- tests/core/git/gitLogHandle.test.ts (106 insertions): Metadata wrapper expectations, fixed deps, proper imports
- tests/core/output/outputGenerate.test.ts (22 insertions): Metadata wrapper mock data
- tests/cli/actions/workers/defaultActionWorker.test.ts (2 modifications): Fixed config schema compliance
- tests/core/git/gitHistory.test.ts (1 modification): Fixed getCommitPatch parameter count
Testable:
- Run: node bin/repomix.cjs --style xml --commit-range HEAD~3..HEAD --stat --parsable-style --output test.xml
- Verify: grep "<git_history>" test.xml shows git_history block present
- Verify: grep "<git_logs>" test.xml | wc -l returns 0 (no git_logs when comprehensive mode active)
- Run: node bin/repomix.cjs --style json --commit-range HEAD~3..HEAD --stat --output test.json
- Verify: jq '.gitCommitHistory' test.json shows object with summary, commits
- Verify: jq '.gitLogs' test.json shows null (not present when comprehensive mode active)
- Run: npm run lint
- Verify: 0 errors, 0 warnings
- Run: npm test
- Verify: all 883 tests pass
…CommitPatches default **Summary**: Consolidates all git commit output into single git_logs block across XML/Markdown/JSON formats while maintaining backward compatibility, and fixes includeCommitPatches default that broke backward compat. **Previous behavior**: - XML/Markdown had separate <git_history> and <git_logs> blocks - includeCommitPatches defaulted to true, forcing --include-logs to use graph path - When --include-logs used alone, tried to fetch git log --graph which failed on repos with <50 commits - Tests checked for "Git Commit History" heading in markdown - JSON output included both logCommits and commits with empty metadata in backward compat mode **What changed**: - src/config/configSchema.ts:125: Change includeCommitPatches default from true to false - src/core/git/gitLogHandle.ts:43-56: Update GitLogResult interface docs to describe additive optional fields - src/core/git/gitLogHandle.ts:129-227: Remove getSimpleGitLogs/getComprehensiveGitLogs split, use single getGitLogs function - src/core/output/outputGenerate.ts:36-76: Remove gitCommitHistoryEnabled field and hasGraphOrSummary variable - src/core/output/outputGeneratorTypes.ts:37: Remove gitCommitHistoryEnabled field from RenderContext - src/core/output/outputStyles/xmlStyle.ts:67-152: Consolidate git_history into git_logs block with additive optional fields - src/core/output/outputStyles/markdownStyle.ts:64-150: Consolidate "Git Commit History" section into "Git Logs" section - src/core/output/outputGenerate.ts:114-165,213-256: Consolidate parsable XML and JSON output to single gitLogs structure - src/core/output/outputGenerate.ts:142,237: Add !renderContext.gitLogCommits condition to prevent outputting commits with empty metadata in backward compat mode - tests/core/output/outputGenerate.test.ts:330,457: Add includeLogs: true to test configs - tests/core/output/outputGenerate.test.ts:374: Update test expectation from "Git Commit History" to "# Git Logs" **Why**: Consolidating into one naturally backward and forward compatible structure eliminates special case handling and makes output format cleaner. Fixing includeCommitPatches default ensures --include-logs alone uses backward compatible git log command, not git log --graph. **Testable**: - Backward compat: `repomix --include-logs --style xml` outputs only git_log_commit entries - Full features: `repomix --include-logs --commit-range HEAD~1..HEAD --stat --graph --summary --style xml` outputs summary, commit_graph, and commits in git_logs block - All 883 tests passing - All linting passing (0 errors, 0 warnings) **Files affected**: - src/config/configSchema.ts: Fix includeCommitPatches default - src/core/git/gitLogHandle.ts: Update docs, simplify function - src/core/output/outputGenerate.ts: Remove mode flag, consolidate output - src/core/output/outputGeneratorTypes.ts: Remove gitCommitHistoryEnabled field - src/core/output/outputStyles/xmlStyle.ts: Consolidate into git_logs block - src/core/output/outputStyles/markdownStyle.ts: Consolidate into Git Logs section - tests/core/output/outputGenerate.test.ts: Update test configs and expectations
…le GitLogCommit structure Summary: Eliminate duplicate commit data structures (logCommits vs commits) by enhancing GitLogCommit interface with progressive optional fields, following DRY principles while maintaining backward compatibility with main branch. Previous behavior (based on git diff): - GitLogResult had two separate arrays: logCommits (core fields) and commits (full metadata with HistoryCommitResult wrapper) - Output generation used extractBasicCommits() to transform commits → logCommits - Templates had conditional logic: render logCommits OR commits, never both - Token counting used logContent string field - Security checking used logContent string field What changed: - src/core/git/gitLogHandle.ts: * Removed HistoryCommitResult interface (lines 24-29) * Enhanced GitLogCommit interface with optional fields: hash, abbreviatedHash, author, committer, parents, body, patch (lines 58-97) * Updated GitLogResult to use only logCommits array, removed logContent and commits fields (lines 42-54) * Full features path populates all fields including extended metadata (lines 193-215) * Backward compat path returns only core fields (lines 236-240) - src/core/output/outputGenerate.ts: * Removed extractBasicCommits() transformation function (lines 36-46) * Simplified createRenderContext to return only gitLogCommits (lines 55-62) * Consolidated XML output to single git_log_commit mapping with progressive optional fields (lines 117-145) * Consolidated JSON output to single logCommits array with progressive optional fields (lines 209-225) - src/core/output/outputGeneratorTypes.ts: * Removed gitLogContent and gitCommitHistoryItems fields from RenderContext (lines 34-37) - src/core/output/outputStyles/xmlStyle.ts: * Moved summary and commit_graph sections before commits for overview-first presentation (lines 98-143) * Removed separate gitCommitHistoryItems loop * Single gitLogCommits loop with conditional rendering of optional fields - src/core/output/outputStyles/markdownStyle.ts: * Removed separate gitCommitHistoryItems loop (lines 100-138) * Single gitLogCommits loop with progressive disclosure of extended fields - src/core/metrics/calculateGitLogMetrics.ts: * Changed from logContent string to JSON.stringify(logCommits) for token counting (lines 22-33) * Maintains graph content in token count calculation - src/core/security/securityCheck.ts: * Changed from logContent string to JSON.stringify(logCommits) for security scanning (lines 49-56) - tests/core/git/gitLogHandle.test.ts: * Updated expectations from commits array with metadata wrapper to logCommits with core fields only (lines 93-105) - tests/core/metrics/calculateGitLogMetrics.test.ts: * Updated mock data from logContent string to logCommits array (multiple test cases) * Validators expect JSON.stringify(logCommits) for token counting - tests/core/output/outputGenerate.test.ts: * Updated mock GitLogResult objects to use logCommits with flattened structure (lines 232-238, 331-343) * Changed XML assertions from commits['@_hash'] to git_log_commit.hash (lines 424-431) * Updated files format expectations to match plain-text rendering Why: - Violates DRY: Two arrays (logCommits, commits) representing same concept - Unnecessary complexity: Conditional output logic to prevent rendering both - Template duplication: Separate iteration loops for same data - Data transformation overhead: extractBasicCommits() converting between formats - Single source of truth: One canonical structure eliminates inconsistencies Implementation approach: - Progressive disclosure pattern: Core fields (date, message, files) always present, extended fields (hash, author, committer, parents, body, patch) optional - Backward compatible: Old parsers ignore unknown optional fields per JSON/XML specs - Forward compatible: Extended fields populate when graph/summary/patches enabled - Additive evolution: Existing gitLogCommits field grows capabilities rather than being replaced Files affected: - src/core/git/gitLogHandle.ts: Enhanced GitLogCommit interface, removed HistoryCommitResult, updated GitLogResult - src/core/output/outputGenerate.ts: Removed extractBasicCommits(), simplified render context - src/core/output/outputGeneratorTypes.ts: Cleaned up RenderContext interface - src/core/output/outputStyles/xmlStyle.ts: Unified template with progressive optional fields - src/core/output/outputStyles/markdownStyle.ts: Unified template with progressive optional fields - src/core/metrics/calculateGitLogMetrics.ts: JSON serialization for token counting - src/core/security/securityCheck.ts: JSON serialization for security scanning - tests/core/git/gitLogHandle.test.ts: Updated expectations for unified structure - tests/core/metrics/calculateGitLogMetrics.test.ts: Updated mock data and assertions - tests/core/output/outputGenerate.test.ts: Updated mock data and XML/JSON expectations Testable outcomes: - Run: npm run test → All 883 tests pass - Run: repomix --include-logs --style xml → Outputs only core fields (date, message, files) - Run: repomix --include-logs --commit-range HEAD~1..HEAD --graph --stat --summary --style xml → Outputs all extended fields (hash, author, committer, parents, body, patch) plus summary and graph sections - Verify: XML/JSON output structure matches main branch baseline for backward compatibility - Verify: Progressive disclosure works - optional fields only present when features enabled Backward compatibility: Maintained with main branch - gitLogCommits field existed in main with same plain-text files format, core fields structure unchanged Implementation note: Token counting and security scanning now use JSON.stringify(logCommits) instead of raw logContent string (removed field), providing consistent serialization for internal metrics
…patch flag mapping
Summary: Eliminate dual simple/enhanced code paths by implementing single git log execution that combines metadata, files, patches, and graph in one subprocess call using git's parameter stacking capability, and fix CLI bug where --stat/--patch flags failed to enable patch fetching.
Previous behavior (based on git diff):
- getGitLogs() had if/else branching (lines 299-362): simple path vs enhanced path
- Enhanced path called getCommitGraph() which looped calling git log -1 per commit for metadata (gitHistory.ts:229)
- Simple path called execGitLog() once for basic date|message + files
- CLI set commitPatchDetail when --stat/--patch specified but left includeCommitPatches as false
- Result: patches never fetched despite user requesting them via --stat/--patch flags
What changed:
- src/cli/actions/defaultAction.ts:330-333:
* Change commitPatchDetail mapping from single field to object with includeCommitPatches: true
* When user specifies --stat/--patch/--numstat/etc, both commitPatchDetail and includeCommitPatches are set
- src/core/git/gitCommand.ts:7,181-281:
* Import PatchDetailLevel type
* Add GitLogCompleteOptions interface (directory, range, maxCommits, includeGraph, patchDetail)
* Add execGitLogComplete() function
* Build git log args array conditionally: base format with metadata fields, add --stat/--patch/etc if patchDetail specified, add --graph --all if includeGraph true, add range or -n maxCommits
* Use separators: \x1E (record separator) between commits, \x1F (field separator) between metadata fields, \x00 (null byte) before file list
* Return raw git log stdout for parsing
- src/core/git/gitHistory.ts:368:
* Export generateMermaidGraph() (was const, now export const for reuse)
- src/core/git/gitLogHandle.ts:4,8-11:
* Import execGitLogComplete, GitLogCompleteOptions from gitCommand
* Import generateMermaidGraph, getTags from gitHistory
* Update deps parameter in getGitLogs() to use new functions
- src/core/git/gitLogHandle.ts:125-245:
* Add ParsedCommitComplete interface with hash, abbreviatedHash, parents, author, committer, message, body, files, optional patch, optional graphPrefix
* Add parseGitLogComplete() function
* Split records on \x1E separator
* Extract graph prefix lines (before first field separator) if hasGraph is true
* Split metadata section from files/patch section on null byte
* Parse metadata by splitting on field separator and stripping graph prefixes from each field
* Split files and patch sections on blank lines (\n\n+)
* Normalize line endings with split(/\r?\n/) to handle CRLF
- src/core/git/gitLogHandle.ts:297-397:
* Remove entire if/else block (enhanced mode vs simple mode branching deleted)
* Calculate needsEnhanced based on includeCommitGraph || includeCommitPatches || includeSummary
* Call execGitLogComplete() once with conditional parameters (range if enhanced, maxCommits otherwise)
* Call parseGitLogComplete() to extract all data from single output
* Map to GitLogCommit array with core fields always present, extended fields only when needsEnhanced
* Build CommitGraph from parsed data by reconstructing ASCII from graphPrefix, building metadata array, generating Mermaid
* Build summary from parsed data (no separate git calls)
- tests/core/git/gitLogHandle.test.ts:67-355:
* Replace getGitLog/getCommitGraph mocks with execGitLogComplete/getTags mocks
* Build mock output using \x1E, \x1F, \x00 separators matching execGitLogComplete format
* Update expected function calls to match GitLogCompleteOptions structure
* Update all assertions to expect {logCommits, graph, summary} return structure
* Add test data with \r\n line endings to verify CRLF handling
Why:
- Dual code paths violated DRY principle with separate implementations for simple vs enhanced
- Enhanced mode made multiple separate git subprocess calls instead of combining parameters
- Git supports combining --pretty + --name-only + --stat + --graph in single command
- Parsing interleaved output once is more efficient than multiple subprocess calls
- CLI bug prevented user-requested patches from appearing in output
Implementation approach:
- Conditional parameter building: start with base args, add flags based on config
- Intelligent parsing: use separators to reliably split commits, fields, files, patches
- Progressive disclosure: parse all data, conditionally include fields in GitLogCommit based on needsEnhanced
- Graph reconstruction: preserve graph prefix lines during parsing, rejoin for ASCII graph
- Single source of truth: one git call, one parse, multiple uses of parsed data
Files affected:
- src/cli/actions/defaultAction.ts: Fix CLI param mapping for patch flags
- src/core/git/gitCommand.ts: Add execGitLogComplete() with conditional arg building
- src/core/git/gitHistory.ts: Export generateMermaidGraph
- src/core/git/gitLogHandle.ts: Add parseGitLogComplete(), consolidate getGitLogs()
- tests/core/git/gitLogHandle.test.ts: Update mocks for consolidated API
Testable outcomes:
- Run: npm run test → All 883 tests pass
- Run: repomix --include-logs → Outputs only core fields (date, message, files)
- Run: repomix --include-logs --graph --stat → Outputs extended fields (hash, author, committer, parents) and patches
- Run: repomix --stat → Patches appear in output (CLI bug fixed)
- Verify: git log called once per execution instead of multiple times
- Verify: Progressive disclosure works - extended fields only when enhancement flags used
Performance: Consolidated multiple git subprocess calls into single git log invocation by using git's parameter stacking capability (--pretty + --name-only + --stat + --graph)
…th implementation Summary: Update MCP and user documentation to accurately describe consolidated git logs implementation where all modes use single optimized code path with progressive field disclosure. Previous behavior (documentation): - configuration.md line 111 referenced removed includeCommitHistory flag - configuration.md had incorrect default values for includeCommitGraph (true → false) and includeCommitPatches (true → false) - llms-install.md documented includeCommitHistory instead of orthogonal flags (includeLogs, includeCommitGraph, includeSummary, includeCommitPatches) - output.md showed old <git_history>/<commits> XML structure from previous implementation What changed: - llms-install.md:64-69: * Remove includeCommitHistory parameter documentation * Add includeLogs, includeCommitGraph, includeSummary, includeCommitPatches parameters * Update commitPatchDetail to list all format options: patch, stat, numstat, shortstat, dirstat, name-only, name-status, raw - llms-install.md:114-119: * Same updates for pack_remote_repository tool - website/client/src/en/guide/configuration.md:111-116: * Remove includeCommitHistory row from configuration table * Add includeSummary row * Correct defaults: includeCommitGraph false (was true), includeCommitPatches false (was true) * Update commitPatchDetail description with all format options - website/client/src/en/guide/configuration.md:173-185: * Remove includeCommitHistory from example config * Add includeSummary with correct default false * Update all git config defaults to match implementation - website/client/src/en/guide/configuration.md:349-372: * Replace "Commit History Analysis" section with "Commit History Enhancement" * Remove includeCommitHistory description * Document that enhancement flags (commitRange, includeCommitGraph, includeSummary, includeCommitPatches) provide structured metadata when combined with includeLogs * Update example config to use includeLogs + enhancement flags instead of includeCommitHistory - website/client/src/en/guide/output.md:246-305: * Update XML structure from <git_history>/<commits> to <git_logs>/<git_log_commit> * Show summary and commit_graph elements with correct structure * Show git_log_commit elements with all fields (date, message, files, hash, abbreviated_hash, author, committer, parents, body, patch) * Files rendered as plain text (backward compatible with main branch) * Update heading from "Commit History Output" context Why: - Documentation must accurately reflect actual API and configuration schema - Users need correct parameter names for MCP tools (pack_codebase, pack_remote_repository) - Configuration examples must show valid flag combinations that match implementation - Default values in docs must match configSchema.ts defaults - XML structure examples must match actual template output Files affected: - llms-install.md: MCP tool parameters for both pack_codebase and pack_remote_repository - website/client/src/en/guide/configuration.md: Config table, defaults, examples, and descriptions - website/client/src/en/guide/output.md: XML structure example for git logs Testable outcomes: - Configuration table defaults match src/config/configSchema.ts default values - Example configs use valid flag names (includeLogs, includeCommitGraph, includeSummary, includeCommitPatches) - XML output structure matches src/core/output/outputStyles/xmlStyle.ts template - MCP parameter names match src/mcp/tools/ implementation
… chars in commits
Previous behavior: Parser used ASCII control characters \x1E (Record Separator)
and \x1F (Unit Separator) as delimiters. These characters CAN appear in git
commit messages, breaking parsing when encountered.
What changed:
- gitCommand.ts: Replace execGitLogComplete with two-pass architecture
- execGitLogStructured: Metadata + files via -z --raw (NUL-terminated)
- execGitLogTextBlob: Patch/stat content with double-NUL separator
- execGitGraph: Separate call avoids prefix interleaving
- All delimiters now use NUL (\x00) which git rejects in commit content
- gitLogHandle.ts: Rewrite parser for robustness and performance
- Incremental parsing by detecting 40-char hex hashes as commit boundaries
- Verify commit boundary by checking abbreviated hash shares prefix
- Handle filenames that look like git hashes (after raw entry lines)
- Pre-compile regexes and add length checks for ~40% performance gain
- gitLogHandle.test.ts: Update test infrastructure
- Use proper 40-char hex hashes in mocks
- Add edge case test for hash-like filenames
- Remove DOUBLE_NUL constant (parser no longer relies on it)
Why: Git rejects commits containing NUL bytes ("a NUL byte in commit log
message not allowed"), making NUL 100% safe as delimiter. ASCII control
characters \x1E/\x1F can appear in messages (rare but possible).
Files affected:
- src/core/git/gitCommand.ts: New two-pass git log functions
- src/core/git/gitLogHandle.ts: Rewritten parser with hash-based boundaries
- tests/core/git/gitLogHandle.test.ts: Updated mocks and added edge case test
Testable: npm run test (884 tests pass), parser handles commits with \x1E/\x1F
…tions'
Previous behavior: CLI and docs used "Enhancement Flags" and "Range Options"
as category names for git log related flags.
What changed: Renamed to "Output Verbosity & Graph Options" across all
documentation and source files:
- src/cli/cliRun.ts: .optionsGroup('Git Log Output Verbosity & Graph Options')
- src/cli/types.ts: comment updated to match
- README.md: section header updated
- website/client/src/en/guide/*: All English docs updated
Why: "Enhancement Flags" was vague and non-descriptive. "Output Verbosity &
Graph Options" clearly describes that these options control the level of
detail (verbosity) in git log output and graph visualization. The commit
range is a form of verbosity since it restricts the amount of output.
Files affected:
- src/cli/cliRun.ts:157-158 - CLI option group name
- src/cli/types.ts:42 - Type comment
- README.md:669 - Section header
- website/client/src/en/guide/command-line-options.md - Merged sections
- website/client/src/en/guide/configuration.md - Updated references
- website/client/src/en/guide/output.md - Updated references
- website/client/src/en/guide/tips/git-commit-history.md - Section header
- website/client/src/en/guide/usage.md - Section header
Previous behavior: All translation files had the Git Commit History Options section in English only. What changed: Properly translated the Git Commit History Options section into each respective language: - de (German): Git-Commit-Verlaufsoptionen - es (Spanish): Opciones del Historial de Commits de Git - fr (French): Options de l'Historique des Commits Git - hi (Hindi): Git कमिट इतिहास विकल्प - id (Indonesian): Opsi Riwayat Commit Git - ja (Japanese): Gitコミット履歴オプション - ko (Korean): Git 커밋 히스토리 옵션 - pt-br (Portuguese-BR): Opções de Histórico de Commits do Git - vi (Vietnamese): Tùy chọn Lịch sử Commit Git - zh-cn (Chinese Simplified): Git提交历史选项 - zh-tw (Chinese Traditional): Git提交歷史選項 Each translation includes: - Section header translated to native language - Introductory paragraph about orthogonal flags - "Diff Format Flags" subheader with mutually exclusive note - All 8 diff format option descriptions - "Output Verbosity & Graph Options" subheader with combinable note - All 3 verbosity/graph option descriptions Why: Proper i18n requires content to be in the user's native language, not just the section headers. Technical terms like git log flags remain in English as they are command-line parameters. Files affected: - website/client/src/de/guide/command-line-options.md - website/client/src/es/guide/command-line-options.md - website/client/src/fr/guide/command-line-options.md - website/client/src/hi/guide/command-line-options.md - website/client/src/id/guide/command-line-options.md - website/client/src/ja/guide/command-line-options.md - website/client/src/ko/guide/command-line-options.md - website/client/src/pt-br/guide/command-line-options.md - website/client/src/vi/guide/command-line-options.md - website/client/src/zh-cn/guide/command-line-options.md - website/client/src/zh-tw/guide/command-line-options.md
…name-only'
Previous behavior: Documentation and TypeScript types claimed commitPatchDetail
defaulted to 'stat', but configSchema.ts:121 actually defaults to 'name-only'.
Additionally, GitLogTextBlobOptions.patchDetail union type was missing three
valid values ('name-only', 'name-status', 'raw'), and patchTypes array omitted
'raw', preventing commitPatchDetail: 'raw' from fetching patch content.
What changed:
- llms-install.md: Fix default from 'stat' to 'name-only' (2 occurrences)
- src/core/git/gitCommand.ts:208: Add 'name-only' | 'name-status' | 'raw' to
GitLogTextBlobOptions.patchDetail union type
- src/core/git/gitLogHandle.ts:241: Add 'raw' to patchTypes array so
commitPatchDetail: 'raw' triggers patch fetching
- src/core/git/gitLogHandle.ts:251-259: Update type cast to match expanded union
- website/client/src/en/guide/configuration.md: Fix default from 'stat' to
'name-only' (3 occurrences: table, JSON example, prose)
Why: CodeRabbit review identified documentation/type mismatches with the actual
config schema. Users configuring commitPatchDetail: 'raw' would silently get no
patch output because 'raw' wasn't in the patchTypes check array.
Testable: npm run lint && npm test (884 tests pass)
6d34ebc to
414ab2d
Compare
|
@ahundt Here are my thoughts: Option naming
Design concerns
Scope
What do you think? |
|
I'm actually using all the params, they are helpful for inspecting and diagnosing good commits and bad commits in a messy history. Also if it were instead set up as commands and/or ordered params the ambiguity would go away. like how there is git rm, not git --rm; and then git rm can itself have params/flags, and those params/flags can also (potentially) work for git log, for example. even if the --include-logs was kept, there could still be ordering to disambiguate. But I don't care that much if you want --git-patch, it is a bit redundant but not the end of the world it would get the job done. |
|
Just following up! |
Merge 632 upstream commits (v1.9.1 → v1.12.0) into the git commit history feature branch. Conflicts resolved: - src/core/output/outputGenerate.ts: accept upstream's template cache, calculateFileLineCounts, export createRenderContext, filePathsByRoot parameter alongside branch's git log field changes and expanded XML/JSON output sections - tests/core/output/outputGenerateDiffs.test.ts: add filePathsByRoot undefined parameter to all generateOutput call sites Post-merge fixes (upstream tests using old GitLogResult interface): - tests/core/output/outputSplit.test.ts: logContent/commits → logCommits - tests/core/packager/produceOutput.test.ts: logContent/commits → logCommits - tests/core/skill/skillSectionGenerators.test.ts: gitLogContent → gitCommitHistorySummary/gitCommitGraph in RenderContext mock Build passes, lint passes (0 errors), 1116/1118 tests pass (2 pre-existing upstream failures in packageJsonParse.test.ts unrelated to this merge).
…ee-dot ranges, and range handling Summary: Address all PR yamadashy#968 review feedback — rename 10 CLI flags to --git- prefix per maintainer request, fix 4 bugs (maxBuffer crash, three-dot range parsing, missing patchTypes entries, --commit-range silently ignored), and allow range+maxCommits together. Previous behavior: - CLI flags --stat, --patch, --graph, etc. had no namespace prefix, risking conflicts - execFileAsync used default 1MB maxBuffer, crashing on large repos with --patch - parseCommitRange split on '..' first, corrupting three-dot ranges like main...feature - patchTypes array omitted 'name-only' and 'name-status', silently dropping those flags - --commit-range without --graph/--patch/--summary was silently ignored - range and maxCommits were mutually exclusive (if/else if) What changed: - src/cli/types.ts: rename 10 CliOptions fields (stat→gitStat, graph→gitGraph, etc.) - src/cli/cliRun.ts: rename 10 CLI flag definitions (--stat→--git-stat, etc.) and update semanticSuggestionMap alias (--graph→--git-graph) - src/cli/actions/defaultAction.ts: update all flag references to use new gitStat/etc. field names, update error message to show --git- prefixed flag names - src/core/git/gitCommand.ts: add GIT_LOG_MAX_BUFFER (50MB) to all 3 exec functions, change range vs maxCommits from if/else-if to two independent if blocks - src/core/git/gitHistory.ts: add separator field to ParsedCommitRange, rewrite parseCommitRange to check for '...' before '..', add GIT_LOG_MAX_BUFFER to all 4 execFileAsync calls, use separator in range reconstruction - src/core/git/gitLogHandle.ts: add 'name-only' and 'name-status' to patchTypes, add hasExplicitRange check so --commit-range works standalone Tests added/updated: - tests/cli/actions/defaultAction.buildCliConfig.test.ts: 7 new tests for renamed flags - tests/core/git/gitCommand.test.ts: 6 new tests for maxBuffer and range+maxCommits - tests/core/git/gitHistory.test.ts: update 4 assertions for maxBuffer, add 2 three-dot range tests with separator field, add separator to all existing parseCommitRange expects - tests/core/git/gitLogHandle.test.ts: 3 new tests for name-only, name-status patchTypes and explicit commitRange without other flags Documentation (17 files): - README.md, website EN (4 files), website i18n (11 files): rename all repomix CLI flag references from --stat/--patch/etc. to --git-stat/--git-patch/etc., preserving git parameter references in descriptions Why: Maintainer (yamadashy) requested --git- prefix to avoid namespace conflicts. Bug fixes address Devin AI code review findings and maxBuffer crash report. Testable: bun run test (1134/1136 pass, 2 pre-existing failures in packageJsonParse), bun run lint (0 errors), bun run build (compiles clean)
| import { execFile } from 'node:child_process'; | ||
| import { promisify } from 'node:util'; | ||
| import { RepomixError } from '../../shared/errorHandle.js'; | ||
| import { logger } from '../../shared/logger.js'; | ||
|
|
||
| const execFileAsync = promisify(execFile); | ||
|
|
||
| /** | ||
| * Commit metadata with full information | ||
| */ | ||
| export interface CommitMetadata { | ||
| hash: string; | ||
| abbreviatedHash: string; | ||
| parents: string[]; | ||
| author: { | ||
| name: string; | ||
| email: string; | ||
| date: string; | ||
| }; | ||
| committer: { | ||
| name: string; | ||
| email: string; | ||
| date: string; | ||
| }; | ||
| message: string; | ||
| body: string; | ||
| files: string[]; | ||
| } | ||
|
|
||
| /** | ||
| * Commit graph with topology information | ||
| */ | ||
| export interface CommitGraph { | ||
| commits: CommitMetadata[]; | ||
| graph: string; // ASCII art graph | ||
| mermaidGraph: string; // Mermaid diagram | ||
| mergeCommits: string[]; | ||
| tags: Record<string, string>; // tag name -> commit hash | ||
| } | ||
|
|
||
| /** | ||
| * Parsed commit range | ||
| */ | ||
| export interface ParsedCommitRange { | ||
| from: string; | ||
| to: string; | ||
| raw: string; | ||
| separator: '..' | '...'; | ||
| } | ||
|
|
||
| /** | ||
| * Detail level for patches (matching git log parameters) | ||
| */ | ||
| export type PatchDetailLevel = | ||
| | 'patch' // git log --patch: line-by-line diffs | ||
| | 'stat' // git log --stat: diffstat histogram | ||
| | 'numstat' // git log --numstat: numeric additions/deletions | ||
| | 'shortstat' // git log --shortstat: one-line summary | ||
| | 'dirstat' // git log --dirstat: directory distribution | ||
| | 'name-only' // git log --name-only: filenames only | ||
| | 'name-status' // git log --name-status: filenames with status | ||
| | 'raw'; // git log --raw: low-level format | ||
|
|
||
| /** | ||
| * Parse and validate a commit range | ||
| * Supports: HEAD~10..HEAD, tag1..tag2, branch1..branch2, commit1..commit2 | ||
| */ | ||
| export const parseCommitRange = (range: string): ParsedCommitRange => { | ||
| if (!range || typeof range !== 'string') { | ||
| throw new RepomixError('Commit range must be a non-empty string'); | ||
| } | ||
|
|
||
| // Handle single commit (treated as commit^..commit) | ||
| if (!range.includes('..')) { | ||
| return { from: `${range}^`, to: range, raw: range, separator: '..' }; | ||
| } | ||
|
|
||
| // Three-dot (...) must be checked before two-dot (..) to avoid mis-split | ||
| const tripleIdx = range.indexOf('...'); | ||
| if (tripleIdx !== -1) { | ||
| const from = range.slice(0, tripleIdx).trim(); | ||
| const to = range.slice(tripleIdx + 3).trim(); | ||
| if (!from || !to) { | ||
| throw new RepomixError(`Invalid commit range format: '${range}'. Expected format: 'from...to'`); | ||
| } | ||
| return { from, to, raw: range, separator: '...' }; | ||
| } | ||
|
|
||
| const [from, to] = range.split('..'); | ||
| if (!from || !to) { | ||
| throw new RepomixError(`Invalid commit range format: '${range}'. Expected format: 'from..to'`); | ||
| } | ||
|
|
||
| return { from: from.trim(), to: to.trim(), raw: range, separator: '..' }; | ||
| }; | ||
|
|
||
| // Null byte delimiter (via git's %x00) to separate format output from --name-only file list | ||
| // Null bytes cannot appear in commit messages or file paths, making this a reliable delimiter | ||
| const NULL_BYTE = '\0'; | ||
| const GIT_LOG_MAX_BUFFER = 50 * 1024 * 1024; // 50MB — git log --patch on large repos | ||
|
|
||
| /** | ||
| * Get full metadata for a specific commit | ||
| */ | ||
| export const getCommitMetadata = async ( | ||
| directory: string, | ||
| hash: string, | ||
| deps = { | ||
| execFileAsync, | ||
| }, | ||
| ): Promise<CommitMetadata> => { | ||
| try { | ||
| // Get commit metadata with fuller format showing author and committer | ||
| // Use %x00 (null byte) as delimiter - it cannot appear in commit messages or file paths | ||
| const formatString = [ | ||
| '%H', // Full hash | ||
| '%h', // Abbreviated hash | ||
| '%P', // Parent hashes (space-separated) | ||
| '%an', // Author name | ||
| '%ae', // Author email | ||
| '%aI', // Author date (ISO 8601) | ||
| '%cn', // Committer name | ||
| '%ce', // Committer email | ||
| '%cI', // Committer date (ISO 8601) | ||
| '%s', // Subject (first line of message) | ||
| '%b', // Body (rest of message) | ||
| '%x00', // Null byte delimiter before file list (git format specifier) | ||
| ].join('%n'); | ||
|
|
||
| const result = await deps.execFileAsync( | ||
| 'git', | ||
| ['-C', directory, 'log', '-1', `--pretty=format:${formatString}`, '--name-only', hash], | ||
| { maxBuffer: GIT_LOG_MAX_BUFFER }, | ||
| ); | ||
|
|
||
| // Split on null byte to separate format output from file list | ||
| const [formatOutput, fileListOutput] = result.stdout.split(NULL_BYTE); | ||
|
|
||
| const lines = formatOutput.split('\n'); | ||
|
|
||
| if (lines.length < 10) { | ||
| throw new RepomixError(`Invalid git log output for commit ${hash}`); | ||
| } | ||
|
|
||
| const [ | ||
| fullHash, | ||
| abbrevHash, | ||
| parents, | ||
| authorName, | ||
| authorEmail, | ||
| authorDate, | ||
| committerName, | ||
| committerEmail, | ||
| committerDate, | ||
| subject, | ||
| ] = lines.slice(0, 10); | ||
|
|
||
| // Lines after subject (index 10+) are the body | ||
| const body = lines.slice(10).join('\n').trim(); | ||
|
|
||
| // Files come after the delimiter | ||
| const files = fileListOutput ? fileListOutput.split('\n').filter(Boolean) : []; | ||
|
|
||
| return { | ||
| hash: fullHash, | ||
| abbreviatedHash: abbrevHash, | ||
| parents: parents ? parents.split(' ').filter(Boolean) : [], | ||
| author: { | ||
| name: authorName, | ||
| email: authorEmail, | ||
| date: authorDate, | ||
| }, | ||
| committer: { | ||
| name: committerName, | ||
| email: committerEmail, | ||
| date: committerDate, | ||
| }, | ||
| message: subject, | ||
| body, | ||
| files, | ||
| }; | ||
| } catch (error) { | ||
| logger.trace('Failed to get commit metadata:', (error as Error).message); | ||
| throw new RepomixError(`Failed to get commit metadata for ${hash}: ${(error as Error).message}`); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Get commit graph with topology | ||
| */ | ||
| export const getCommitGraph = async ( | ||
| directory: string, | ||
| range: string, | ||
| deps = { | ||
| execFileAsync, | ||
| parseCommitRange, | ||
| getCommitMetadata, | ||
| getTags, | ||
| }, | ||
| ): Promise<CommitGraph> => { | ||
| try { | ||
| const parsedRange = deps.parseCommitRange(range); | ||
|
|
||
| // Get ASCII graph | ||
| const graphResult = await deps.execFileAsync( | ||
| 'git', | ||
| [ | ||
| '-C', | ||
| directory, | ||
| 'log', | ||
| '--graph', | ||
| '--oneline', | ||
| '--decorate', | ||
| '--all', | ||
| `${parsedRange.from}${parsedRange.separator}${parsedRange.to}`, | ||
| ], | ||
| { maxBuffer: GIT_LOG_MAX_BUFFER }, | ||
| ); | ||
|
|
||
| // Get list of commit hashes in range | ||
| const hashesResult = await deps.execFileAsync( | ||
| 'git', | ||
| ['-C', directory, 'log', '--pretty=format:%H', `${parsedRange.from}${parsedRange.separator}${parsedRange.to}`], | ||
| { maxBuffer: GIT_LOG_MAX_BUFFER }, | ||
| ); | ||
|
|
||
| const hashes = hashesResult.stdout.split('\n').filter(Boolean); | ||
|
|
||
| // Get metadata for each commit | ||
| const commits = await Promise.all(hashes.map((hash) => deps.getCommitMetadata(directory, hash))); | ||
|
|
||
| // Identify merge commits (commits with multiple parents) | ||
| const mergeCommits = commits.filter((c) => c.parents.length > 1).map((c) => c.hash); | ||
|
|
||
| // Get tags | ||
| const tags = await deps.getTags(directory); | ||
|
|
||
| // Generate Mermaid graph | ||
| const mermaidGraph = generateMermaidGraph(commits, tags); | ||
|
|
||
| return { | ||
| commits, | ||
| graph: graphResult.stdout, | ||
| mermaidGraph, | ||
| mergeCommits, | ||
| tags, | ||
| }; | ||
| } catch (error) { | ||
| logger.trace('Failed to get commit graph:', (error as Error).message); | ||
| throw new RepomixError(`Failed to get commit graph: ${(error as Error).message}`); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Get git tags mapping | ||
| */ | ||
| export const getTags = async ( | ||
| directory: string, | ||
| deps = { | ||
| execFileAsync, | ||
| }, | ||
| ): Promise<Record<string, string>> => { | ||
| try { | ||
| const result = await deps.execFileAsync('git', [ | ||
| '-C', | ||
| directory, | ||
| 'tag', | ||
| '-l', | ||
| '--format=%(refname:short) %(objectname)', | ||
| ]); | ||
|
|
||
| const tags: Record<string, string> = {}; | ||
| const lines = result.stdout.split('\n').filter(Boolean); | ||
|
|
||
| for (const line of lines) { | ||
| const [tag, hash] = line.split(' '); | ||
| if (tag && hash) { | ||
| tags[tag] = hash; | ||
| } | ||
| } | ||
|
|
||
| return tags; | ||
| } catch (error) { | ||
| logger.trace('Failed to get tags:', (error as Error).message); | ||
| return {}; | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Get patch for a commit with configurable detail level | ||
| */ | ||
| export const getCommitPatch = async ( | ||
| directory: string, | ||
| hash: string, | ||
| detailLevel: PatchDetailLevel = 'stat', | ||
| includeSummary = false, | ||
| deps = { | ||
| execFileAsync, | ||
| }, | ||
| ): Promise<string> => { | ||
| try { | ||
| const args = ['-C', directory, 'show', '--no-color']; | ||
|
|
||
| switch (detailLevel) { | ||
| case 'patch': | ||
| // Full patch with line-by-line diffs (git log --patch) | ||
| args.push('--patch'); | ||
| break; | ||
| case 'stat': | ||
| // Diffstat histogram (git log --stat) | ||
| args.push('--stat'); | ||
| break; | ||
| case 'numstat': | ||
| // Numeric additions/deletions per file (git log --numstat) | ||
| args.push('--numstat'); | ||
| break; | ||
| case 'shortstat': | ||
| // One-line summary of changes (git log --shortstat) | ||
| args.push('--shortstat'); | ||
| break; | ||
| case 'dirstat': | ||
| // Directory change distribution (git log --dirstat) | ||
| args.push('--dirstat'); | ||
| break; | ||
| case 'name-only': | ||
| // Filenames only (git log --name-only) | ||
| args.push('--name-only'); | ||
| break; | ||
| case 'name-status': | ||
| // Filenames with A/M/D/R status (git log --name-status) | ||
| args.push('--name-status'); | ||
| break; | ||
| case 'raw': | ||
| // Low-level format with SHA hashes and modes (git log --raw) | ||
| args.push('--raw'); | ||
| break; | ||
| default: | ||
| throw new RepomixError(`Invalid detail level: ${detailLevel}`); | ||
| } | ||
|
|
||
| // Add --summary flag if requested (shows file operations like creates, renames, mode changes) | ||
| if (includeSummary) { | ||
| args.push('--summary'); | ||
| } | ||
|
|
||
| args.push(hash); | ||
|
|
||
| const result = await deps.execFileAsync('git', args, { maxBuffer: GIT_LOG_MAX_BUFFER }); | ||
| return result.stdout; | ||
| } catch (error) { | ||
| logger.trace('Failed to get commit patch:', (error as Error).message); | ||
| throw new RepomixError(`Failed to get patch for commit ${hash}: ${(error as Error).message}`); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Escape special characters for Mermaid string literals | ||
| */ | ||
| const escapeMermaidString = (str: string): string => { | ||
| return str.replace(/"/g, "'").replace(/\n/g, ' '); | ||
| }; | ||
|
|
||
| /** | ||
| * Generate Mermaid diagram from commits | ||
| * Note: Mermaid gitGraph has limited syntax - it doesn't support arbitrary | ||
| * commit histories with merge visualization without branch context. | ||
| * We generate a simplified linear view with merge indicators. | ||
| */ | ||
| export const generateMermaidGraph = (commits: CommitMetadata[], tags: Record<string, string>): string => { | ||
| const lines: string[] = []; | ||
| lines.push('gitGraph'); | ||
|
|
||
| // Reverse to show oldest first | ||
| const reversed = [...commits].reverse(); | ||
|
|
||
| for (const commit of reversed) { | ||
| const shortHash = commit.abbreviatedHash; | ||
| // Escape quotes and limit message length | ||
| const shortMessage = escapeMermaidString(commit.message.substring(0, 40)); | ||
|
|
||
| // Check if this commit has a tag | ||
| const tagForCommit = Object.entries(tags).find(([, hash]) => hash === commit.hash)?.[0]; | ||
|
|
||
| // Build commit line with optional tag | ||
| // Mermaid syntax: commit id: "..." [tag: "..."] [type: ...] | ||
| const isMerge = commit.parents.length > 1; | ||
| const messageWithMerge = isMerge ? `(merge) ${shortMessage}` : shortMessage; | ||
| const commitId = `${shortHash}: ${messageWithMerge}`; | ||
|
|
||
| let commitLine = ` commit id: "${commitId}"`; | ||
| if (tagForCommit) { | ||
| commitLine += ` tag: "${escapeMermaidString(tagForCommit)}"`; | ||
| } | ||
| if (isMerge) { | ||
| commitLine += ' type: HIGHLIGHT'; | ||
| } | ||
|
|
||
| lines.push(commitLine); | ||
| } | ||
|
|
||
| return lines.join('\n'); | ||
| }; |
There was a problem hiding this comment.
🟡 New file gitHistory.ts exceeds 250-line limit from AGENTS.md
The new file src/core/git/gitHistory.ts is 402 lines, far exceeding the 250-line limit specified in AGENTS.md: "If a file exceeds 250 lines, then the assistant shall split it into multiple files based on functionality." The file contains several distinct functional areas (commit range parsing, commit metadata fetching, commit graph generation, tag fetching, patch retrieval, and Mermaid graph generation) that could be split into separate modules.
Prompt for agents
Split src/core/git/gitHistory.ts (402 lines) into smaller files to comply with the 250-line limit in AGENTS.md. Suggested split:
1. src/core/git/gitCommitRange.ts - parseCommitRange function and ParsedCommitRange interface (~30 lines)
2. src/core/git/gitCommitMetadata.ts - getCommitMetadata, CommitMetadata interface (~90 lines)
3. src/core/git/gitCommitGraph.ts - getCommitGraph, CommitGraph interface, generateMermaidGraph, escapeMermaidString (~120 lines)
4. src/core/git/gitTags.ts - getTags function (~30 lines)
5. src/core/git/gitCommitPatch.ts - getCommitPatch, PatchDetailLevel type (~70 lines)
Each file should export its public types and functions, and the current gitHistory.ts can become a barrel file re-exporting everything for backward compatibility.
Was this helpful? React with 👍 or 👎 to provide feedback.
| // ===== Two-Pass Parser ===== | ||
| // | ||
| // Architecture: Separates structured data from text blobs for robust parsing | ||
| // - Pass 1 (execGitLogStructured): NUL-terminated metadata + raw file entries | ||
| // - Pass 2 (execGitLogTextBlob): Patch/stat content matched by array index | ||
| // - Pass 3 (execGitGraph): Separate call avoids graph prefix interleaving | ||
| // | ||
| // NUL (\x00) is used as the ONLY delimiter because git REJECTS commits containing | ||
| // NUL bytes. This makes parsing 100% robust - NUL cannot appear in commit messages, | ||
| // author names, emails, or any other commit content. | ||
| // | ||
| // With -z --raw, git uses double-NUL (\x00\x00) between commits, providing a safe | ||
| // record separator that cannot be forged by commit content. | ||
|
|
||
| interface ParsedCommit { | ||
| hash: string; | ||
| abbrevHash: string; | ||
| parents: string[]; | ||
| author: { name: string; email: string; date: string }; | ||
| committer: { name: string; email: string; date: string }; | ||
| message: string; | ||
| body: string; | ||
| files: string[]; | ||
| patch?: string; | ||
| } | ||
|
|
||
| const parseGitLog = (rawLogOutput: string, recordSeparator = GIT_LOG_RECORD_SEPARATOR): GitLogCommit[] => { | ||
| if (!rawLogOutput.trim()) { | ||
| return []; | ||
| } | ||
| // Pre-compiled regexes for performance | ||
| const HASH_REGEX = /^[0-9a-f]{40}$/i; | ||
| const ABBREV_HASH_REGEX = /^[0-9a-f]{4,12}$/i; | ||
|
|
||
| /** Fast check if string could be a 40-char hex hash (length + first char check before regex) */ | ||
| const isHash = (s: string): boolean => s.length === 40 && HASH_REGEX.test(s); | ||
|
|
||
| /** Fast check if string looks like abbreviated hash sharing prefix with full hash */ | ||
| const isAbbrevOf = (abbrev: string, full: string): boolean => | ||
| abbrev.length >= 4 && abbrev.length <= 12 && ABBREV_HASH_REGEX.test(abbrev) && full.startsWith(abbrev); | ||
|
|
||
| const commits: GitLogCommit[] = []; | ||
| // Split by record separator used in git log output | ||
| // This is more robust than splitting by double newlines, as commit messages may contain newlines | ||
| const logEntries = rawLogOutput.split(recordSeparator).filter(Boolean); | ||
| /** | ||
| * Parse output from execGitLogStructured with optional patch content from execGitLogTextBlob | ||
| * | ||
| * Input format from -z --raw (all fields NUL-separated): | ||
| * - Fields 0-10: hash, abbrevHash, parents, authorName, authorEmail, authorDate, | ||
| * committerName, committerEmail, committerDate, subject, body | ||
| * - Fields 11+: Raw file entries ":mode mode blob blob STATUS" followed by filename | ||
| * - Next commit starts when we see another 40-char hex hash | ||
| * | ||
| * NUL is 100% safe because git rejects commits containing NUL bytes in any content. | ||
| * We parse incrementally rather than splitting on double-NUL since empty fields | ||
| * also produce double-NUL sequences. | ||
| */ | ||
| const parseStructuredOutput = (output: string, patchOutput?: string): ParsedCommit[] => { | ||
| if (!output) return []; | ||
|
|
||
| // Split on single NUL - all fields are NUL-separated | ||
| const parts = output.split('\x00'); | ||
| const patches = patchOutput ? patchOutput.split('\x00\x00').filter(Boolean) : []; | ||
| const commits: ParsedCommit[] = []; | ||
| let i = 0; | ||
|
|
||
| while (i < parts.length) { | ||
| // Find start of commit (40-char hex hash) - use length check first for speed | ||
| const hashRaw = parts[i]; | ||
| if (!hashRaw) { | ||
| i++; | ||
| continue; | ||
| } | ||
| const hash = hashRaw.trim(); | ||
| if (!isHash(hash)) { | ||
| i++; | ||
| continue; | ||
| } | ||
|
|
||
| for (const entry of logEntries) { | ||
| // Split on both \n and \r\n to handle different line ending formats across platforms | ||
| const lines = entry.split(/\r?\n/).filter((line) => line.trim() !== ''); | ||
| if (lines.length === 0) continue; | ||
| // Need at least 11 fields for metadata | ||
| if (i + 10 >= parts.length) break; | ||
|
|
||
| // First line contains date and message separated by | | ||
| const firstLine = lines[0]; | ||
| const separatorIndex = firstLine.indexOf('|'); | ||
| if (separatorIndex === -1) continue; | ||
| // Extract metadata fields - trim once and store | ||
| const abbrev = parts[i + 1]?.trim() || ''; | ||
| const parents = parts[i + 2]?.trim() || ''; | ||
| const aName = parts[i + 3]?.trim() || ''; | ||
| const aEmail = parts[i + 4]?.trim() || ''; | ||
| const aDate = parts[i + 5]?.trim() || ''; | ||
| const cName = parts[i + 6]?.trim() || ''; | ||
| const cEmail = parts[i + 7]?.trim() || ''; | ||
| const cDate = parts[i + 8]?.trim() || ''; | ||
| const subject = parts[i + 9]?.trim() || ''; | ||
| const body = parts[i + 10]?.trim() || ''; | ||
|
|
||
| const date = firstLine.substring(0, separatorIndex); | ||
| const message = firstLine.substring(separatorIndex + 1); | ||
| i += 11; // Move past metadata fields | ||
|
|
||
| // Remaining lines are file paths | ||
| const files = lines.slice(1).filter((line) => line.trim() !== ''); | ||
| // Parse raw file entries until we hit the next commit or end | ||
| // Raw entry format: ":mode mode blob blob STATUS" followed by filename | ||
| const files: string[] = []; | ||
| while (i < parts.length) { | ||
| const partRaw = parts[i]; | ||
| if (!partRaw) { | ||
| i++; | ||
| continue; | ||
| } | ||
|
|
||
| // Check for raw entry (starts with ':') - most common case in file section | ||
| const firstChar = partRaw[0]; | ||
| if (firstChar === ':' || (firstChar === '\n' && partRaw[1] === ':')) { | ||
| i++; | ||
| const filename = parts[i]?.trim(); | ||
| if (filename) files.push(filename); | ||
| i++; | ||
| continue; | ||
| } | ||
|
|
||
| // Check for next commit boundary - only if length matches hash (40 chars) | ||
| const part = partRaw.trim(); | ||
| if (part.length === 40 && isHash(part)) { | ||
| const nextPart = parts[i + 1]?.trim(); | ||
| if (nextPart && isAbbrevOf(nextPart, part)) { | ||
| break; // This is definitely a new commit | ||
| } | ||
| } | ||
|
|
||
| i++; | ||
| } | ||
|
|
||
| commits.push({ | ||
| date, | ||
| message, | ||
| hash, | ||
| abbrevHash: abbrev, | ||
| parents: parents.split(' ').filter(Boolean), | ||
| author: { name: aName, email: aEmail, date: aDate }, | ||
| committer: { name: cName, email: cEmail, date: cDate }, | ||
| message: subject, | ||
| body, | ||
| files, | ||
| ...(patches[commits.length] && { patch: patches[commits.length].trim() }), | ||
| }); | ||
| } | ||
|
|
||
| return commits; | ||
| }; |
There was a problem hiding this comment.
🟡 Modified file gitLogHandle.ts exceeds 250-line limit from AGENTS.md
The file src/core/git/gitLogHandle.ts grew from 113 lines to 322 lines in this PR, exceeding the 250-line limit specified in AGENTS.md: "If a file exceeds 250 lines, then the assistant shall split it into multiple files based on functionality." The file contains the two-pass parser (parseStructuredOutput with helper functions), type definitions, and the public API (getGitLog, getGitLogs) which could be split.
Prompt for agents
Split src/core/git/gitLogHandle.ts (322 lines) into smaller files to comply with the 250-line limit in AGENTS.md. Suggested split:
1. src/core/git/gitLogParser.ts - Move the parseStructuredOutput function along with ParsedCommit interface, HASH_REGEX, ABBREV_HASH_REGEX, isHash, isAbbrevOf helper functions (lines 76-197, ~120 lines)
2. src/core/git/gitLogHandle.ts - Keep the type definitions (GitLogResult, GitLogCommit, HistorySummary), public API functions (getGitLog, getGitLogs), and constants (~130 lines)
The parser module would export parseStructuredOutput which gitLogHandle.ts imports.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
@yamadashy Thanks for the feedback on the option design! I've pushed a commit addressing all the points you raised, plus the bugs found by Devin and CodeRabbit. PR #968 Issue Resolution Summaryyamadashy (maintainer) feedback, all 3 addressed:
Devin AI review bugs, all 3 addressed:
CodeRabbit review, all addressed:
Capability Regression Check, no regressions:
Happy to make any further adjustments. Thanks again for the great tool! |
…h message
logger.info('Fetched N commits') at gitLogHandle.ts:265 was the only
logger.info call in src/core/git/ (29 other calls all use logger.trace).
This polluted normal CLI output since INFO is the default log level.
Changed to logger.trace so it only appears in --verbose mode, matching
the codebase convention. Also removed emoji from the log message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…flag injection
git log --output=/tmp/evil HEAD~5..HEAD writes to an arbitrary file because
execFile passes the value directly as a git arg. execFile prevents shell
injection but git itself interprets leading '--' as flags, not revision ranges.
Verified: git log --output=/tmp/test and git log --pretty=INJECTED both work.
Adds validateCommitRange() in gitCommand.ts (mirrors existing validateGitUrl
pattern) and calls it in execGitLogStructured, execGitLogTextBlob, execGitGraph
before building the args array, so execFileAsync is never called with a malicious
range. Also adds the same guard in parseCommitRange() in gitHistory.ts, and adds
a z.string().refine() check in configSchema.ts to reject injections from config
files as well.
What changed:
- src/core/git/gitCommand.ts: add validateCommitRange(), call it in all 3 exec fns
- src/core/git/gitHistory.ts: reject range.startsWith('-') in parseCommitRange()
- src/config/configSchema.ts: add .refine() to both commitRange schema entries
Tests added (11 new, 1147 total, 1145 pass):
- tests/core/git/gitCommand.test.ts: validateCommitRange unit tests + injection
tests for all 3 exec functions confirming execFileAsync is never reached
- tests/core/git/gitHistory.test.ts: parseCommitRange throws for --output=,
--pretty=, and -n injection attempts
…parser git log -z --raw outputs two NUL-separated filenames for rename (R) and copy (C) entries: old-name\0new-name. The parser only consumed the first, dropping the destination filename from the files list. Fix: detect R/C status from the raw entry header line, then consume the extra destination filename part when present. Add TDD tests for both rename and copy entries verifying both filenames appear in the result.
There was a problem hiding this comment.
Devin Review found 2 new potential issues.
⚠️ 1 issue in files not directly in the diff
⚠️ Plain text template not updated with new git commit history fields (src/core/output/outputStyles/plainStyle.ts:80-96)
The plain text output template (src/core/output/outputStyles/plainStyle.ts) was not updated to render any of the new git commit history fields (hash, author, committer, parents, body, patch, graph, summary). Both the XML template (xmlStyle.ts) and Markdown template (markdownStyle.ts) were updated with these new fields, but the plain text template still only renders the basic date, message, and files fields. This means users selecting --style plain with --git-graph, --git-patch, etc. will silently lose the enhanced data they requested.
View 11 additional findings in Devin Review.
| directory, | ||
| 'tag', | ||
| '-l', | ||
| '--format=%(refname:short) %(objectname)', |
There was a problem hiding this comment.
🟡 Annotated git tags never match commits due to %(objectname) returning tag object hash instead of commit hash
The getTags function at src/core/git/gitHistory.ts:275 uses %(objectname) in the --format string. For lightweight tags, %(objectname) correctly returns the commit hash. However, for annotated tags (created with git tag -a), %(objectname) returns the tag object hash, not the underlying commit hash. This means that when generateMermaidGraph at src/core/git/gitHistory.ts:388 tries to match tags to commits via hash === commit.hash, annotated tags will never match because the tag object hash differs from the commit hash. Annotated tags are very common (e.g., GitHub release tags are annotated). The result is that annotated tags won't appear as markers on the Mermaid graph, and the tags section in the output will list incorrect hashes (tag object hashes instead of commit hashes).
The fix is to use %(*objectname) (the dereferenced/peeled hash) for annotated tags, falling back to %(objectname) for lightweight tags.
| '--format=%(refname:short) %(objectname)', | |
| '--format=%(refname:short) %(if)%(*objectname)%(then)%(*objectname)%(else)%(objectname)%(end)', |
Was this helpful? React with 👍 or 👎 to provide feedback.
Choose which to pack: commit graphs, metadata, and diffs to analyze changes
Enhances
--include-logsto include structured git commit data with optional diff/patch content, using orthogonal flags that mirror git log's own options (prefixed with--git-to avoid namespace conflicts).Why
To evaluate repository history for cleanup, review, or AI analysis, info is needed beyond commit messages:
This adds these capabilities through flags that match git log's parameter structure.
Features
Graph Visualization (
--git-graph) — NEWgit log --graph --oneline --allCommit Metadata
With
--include-logs(existing): date, message, filesAdd any output verbosity/graph option (
--git-graph/--git-stat/--git-patch/--git-summary/etc.) for NEW fields:Patch/Diff Content (
--git-patch,--git-stat, etc.) — NEWInclude actual code changes — see exactly what lines were added, removed, or modified per commit. Choose detail level with diff format options.
Tag Mapping (
--git-graph) — NEWMaps git tags to commit hashes for identifying release boundaries. Included with graph visualization.
CLI Options
Diff Format Options (mutually exclusive, mirrors git log):
--git-name-only--name-only--git-stat--stat--git-patch--patch--git-numstat--numstat--git-shortstat--shortstat--git-dirstat--dirstat--git-name-status--name-status--git-raw--rawOutput Verbosity & Graph Options (combinable with diff format):
--git-graph--graph --all--git-summary--summary--commit-range <range><range>HEAD~50..HEAD--include-logs-count <n>-nUsage Examples
Performance
Measured over 10 runs each on repomix repo (median with min-max range, lower duration is better):
Test Suite Comparison:
gitLogHandle.test.ts (10 runs each, tests the changed code, lower duration is better):
Repomix Execution (
repomix --include-logson repomix repo, 10 runs each, lower duration is better):Backwards Compatibility
Preserved:
<git_logs>,<git_log_commit>,<date>,<message>,<files>--include-logsalone produces equivalent outputChanged (Minor):
2025-11-20 23:12:33 +0900→2025-11-26T04:30:33-05:00(strict ISO 8601)%aIinstead of%adwith--date=isoTseparator and colon in timezone--git-stat,--git-patch,--git-graph,--git-summary,--commit-range, etc.) are purely additive.New optional sections (only when using enhancement flags):
<hash>,<author>,<committer>,<parents>,<body>,<patch>per commit<summary>section with commit counts and range info<commit_graph>section with ASCII and Mermaid visualizationsOutput Structure
GitLogCommitwith progressive fields:Checklist
npm run test— 1136 tests (1134 pass, 2 pre-existing failures inpackageJsonParse.test.tsalso fail onmainat72d53a4a)npm run lint— 0 errors, 0 warnings--include-logsbehavior preserved)