Skip to main content
Glama
MIT License
27,120
19,780
  • Linux
  • Apple
gitLogHandle.test.ts9.26 kB
import { beforeEach, describe, expect, test, vi } from 'vitest'; import type { RepomixConfigMerged } from '../../../src/config/configSchema.js'; import { GIT_LOG_FORMAT_SEPARATOR, GIT_LOG_RECORD_SEPARATOR, getGitLog, getGitLogs, } from '../../../src/core/git/gitLogHandle.js'; import { RepomixError } from '../../../src/shared/errorHandle.js'; import { logger } from '../../../src/shared/logger.js'; vi.mock('../../../src/shared/logger'); describe('gitLogHandle', () => { beforeEach(() => { vi.resetAllMocks(); }); describe('getGitLog', () => { test('should return git log content when directory is a git repository', async () => { const mockExecGitLog = vi.fn().mockResolvedValue('mock log content'); const mockIsGitRepository = vi.fn().mockResolvedValue(true); const result = await getGitLog('/test/dir', 10, { execGitLog: mockExecGitLog, isGitRepository: mockIsGitRepository, }); expect(result).toBe('mock log content'); expect(mockIsGitRepository).toHaveBeenCalledWith('/test/dir'); expect(mockExecGitLog).toHaveBeenCalledWith('/test/dir', 10, GIT_LOG_FORMAT_SEPARATOR); }); test('should return empty string when directory is not a git repository', async () => { const mockExecGitLog = vi.fn(); const mockIsGitRepository = vi.fn().mockResolvedValue(false); const result = await getGitLog('/test/dir', 10, { execGitLog: mockExecGitLog, isGitRepository: mockIsGitRepository, }); expect(result).toBe(''); expect(mockIsGitRepository).toHaveBeenCalledWith('/test/dir'); expect(mockExecGitLog).not.toHaveBeenCalled(); expect(logger.trace).toHaveBeenCalledWith('Directory /test/dir is not a git repository'); }); test('should throw error when git log command fails', async () => { const mockError = new Error('git command failed'); const mockExecGitLog = vi.fn().mockRejectedValue(mockError); const mockIsGitRepository = vi.fn().mockResolvedValue(true); await expect( getGitLog('/test/dir', 10, { execGitLog: mockExecGitLog, isGitRepository: mockIsGitRepository, }), ).rejects.toThrow('git command failed'); expect(logger.trace).toHaveBeenCalledWith('Failed to get git log:', 'git command failed'); }); }); describe('getGitLogs', () => { test('should return git logs when includeLogs is enabled', async () => { const mockLogContent = `${GIT_LOG_RECORD_SEPARATOR}2024-01-01 10:00:00 +0900|Initial commit file1.txt file2.txt ${GIT_LOG_RECORD_SEPARATOR}2024-01-02 11:00:00 +0900|Add feature src/feature.ts`; const mockGetGitLog = vi.fn().mockResolvedValue(mockLogContent); const config: RepomixConfigMerged = { cwd: '/project', output: { git: { includeLogs: true, includeLogsCount: 25, }, }, } as RepomixConfigMerged; const result = await getGitLogs(['/project/src'], config, { getGitLog: mockGetGitLog, }); expect(result).toEqual({ logContent: mockLogContent, commits: [ { date: '2024-01-01 10:00:00 +0900', message: 'Initial commit', files: ['file1.txt', 'file2.txt'], }, { date: '2024-01-02 11:00:00 +0900', message: 'Add feature', files: ['src/feature.ts'], }, ], }); expect(mockGetGitLog).toHaveBeenCalledWith('/project/src', 25); }); test('should return undefined when includeLogs is disabled', async () => { const config: RepomixConfigMerged = { cwd: '/project', output: { git: { includeLogs: false, }, }, } as RepomixConfigMerged; const result = await getGitLogs(['/project/src'], config); expect(result).toBeUndefined(); }); test('should use default commit count when includeLogsCount is not specified', async () => { const mockGetGitLog = vi.fn().mockResolvedValue(`${GIT_LOG_RECORD_SEPARATOR}2024-01-01 10:00:00 +0900|Test commit test.txt`); const config: RepomixConfigMerged = { cwd: '/project', output: { git: { includeLogs: true, }, }, } as RepomixConfigMerged; await getGitLogs(['/project/src'], config, { getGitLog: mockGetGitLog, }); expect(mockGetGitLog).toHaveBeenCalledWith('/project/src', 50); }); test('should use first directory as git root', async () => { const mockGetGitLog = vi.fn().mockResolvedValue(''); const config: RepomixConfigMerged = { cwd: '/fallback', output: { git: { includeLogs: true, }, }, } as RepomixConfigMerged; await getGitLogs(['/first/dir', '/second/dir'], config, { getGitLog: mockGetGitLog, }); expect(mockGetGitLog).toHaveBeenCalledWith('/first/dir', 50); }); test('should fallback to config.cwd when no directories provided', async () => { const mockGetGitLog = vi.fn().mockResolvedValue(''); const config: RepomixConfigMerged = { cwd: '/fallback', output: { git: { includeLogs: true, }, }, } as RepomixConfigMerged; await getGitLogs([], config, { getGitLog: mockGetGitLog, }); expect(mockGetGitLog).toHaveBeenCalledWith('/fallback', 50); }); test('should throw RepomixError when getGitLog fails', async () => { const mockError = new Error('git failed'); const mockGetGitLog = vi.fn().mockRejectedValue(mockError); const config: RepomixConfigMerged = { cwd: '/project', output: { git: { includeLogs: true, }, }, } as RepomixConfigMerged; await expect( getGitLogs(['/project'], config, { getGitLog: mockGetGitLog, }), ).rejects.toThrow(RepomixError); await expect( getGitLogs(['/project'], config, { getGitLog: mockGetGitLog, }), ).rejects.toThrow('Failed to get git logs: git failed'); }); test('should handle empty git log output', async () => { const mockGetGitLog = vi.fn().mockResolvedValue(''); const config: RepomixConfigMerged = { cwd: '/project', output: { git: { includeLogs: true, }, }, } as RepomixConfigMerged; const result = await getGitLogs(['/project'], config, { getGitLog: mockGetGitLog, }); expect(result).toEqual({ logContent: '', commits: [], }); }); test('should parse git log correctly with malformed separator content', async () => { // Test behavior when log content doesn't match expected separator const malformedLogContent = 'random content without separator'; const mockGetGitLog = vi.fn().mockResolvedValue(malformedLogContent); const config: RepomixConfigMerged = { cwd: '/project', output: { git: { includeLogs: true, }, }, } as RepomixConfigMerged; const result = await getGitLogs(['/project'], config, { getGitLog: mockGetGitLog, }); // Should return empty commits array when content cannot be parsed properly expect(result?.commits).toEqual([]); expect(result?.logContent).toBe(malformedLogContent); }); test('should handle Windows line endings (CRLF) correctly', async () => { // Test with Windows-style line endings (\r\n) const mockLogContent = `${GIT_LOG_RECORD_SEPARATOR}2024-01-01 10:00:00 +0900|Windows commit\r\nfile1.txt\r\nfile2.txt`; const mockGetGitLog = vi.fn().mockResolvedValue(mockLogContent); const config: RepomixConfigMerged = { cwd: '/project', output: { git: { includeLogs: true, }, }, } as RepomixConfigMerged; const result = await getGitLogs(['/project'], config, { getGitLog: mockGetGitLog, }); expect(result?.commits).toEqual([ { date: '2024-01-01 10:00:00 +0900', message: 'Windows commit', files: ['file1.txt', 'file2.txt'], }, ]); }); test('should handle mixed line endings correctly', async () => { // Test with mixed Unix (\n) and Windows (\r\n) line endings const mockLogContent = `${GIT_LOG_RECORD_SEPARATOR}2024-01-01 10:00:00 +0900|Mixed line endings\nfile1.txt\r\nfile2.txt`; const mockGetGitLog = vi.fn().mockResolvedValue(mockLogContent); const config: RepomixConfigMerged = { cwd: '/project', output: { git: { includeLogs: true, }, }, } as RepomixConfigMerged; const result = await getGitLogs(['/project'], config, { getGitLog: mockGetGitLog, }); expect(result?.commits).toEqual([ { date: '2024-01-01 10:00:00 +0900', message: 'Mixed line endings', files: ['file1.txt', 'file2.txt'], }, ]); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yamadashy/repomix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server