Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.1.3 Enhanced Buffer Handling for Large Diffs in evergit commit Command #6

Merged
merged 9 commits into from
Nov 19, 2024
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,25 @@ This file is written automatically by the [version bump script](version-bump.ts)
#### Fixes

- Added a success message after the commit operation is performed.

## [0.1.3] - 2024-11-19

![Increment](https://img.shields.io/badge/patch-purple)

### Changelog Summary

- **Enhancements:**

- Improved buffer handling in the `getDiffForStagedFiles` function.
- Enhanced the CLI main function and added corresponding tests.

- **Testing:**

- Added integration tests for Git utilities.

- **Code Quality:**

- Prettified codebase for improved readability.

- **Merges:**
- Merged branch 'dev' from the repository.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# EverGit

![Version](https://img.shields.io/badge/version-0.1.2-blue)
![Version](https://img.shields.io/badge/version-0.1.3-blue)

![TypeScript](https://img.shields.io/badge/typescript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)
![OpenAI](https://img.shields.io/badge/OpenAI-00A79D?style=for-the-badge&logo=openai&logoColor=white)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "evergit",
"version": "0.1.2",
"version": "0.1.3",
"description": "A CLI tool for generating commit messages that adhere to the Evergreen ILS project's commit policy.",
"main": "src/main.ts",
"bin": {
Expand Down
12 changes: 7 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import commit from './cmd/commit';

const program = new Command();

function main(): void {
program.name('evergit').description('Automate your Evergreen ILS git workflow').version('0.1.2');
export async function main(args = process.argv): Promise<void> {
program.name('evergit').description('Automate your Evergreen ILS git workflow').version('0.1.3');

program
.command('commit')
Expand All @@ -17,11 +17,13 @@ function main(): void {
await commit(options.model, options.all);
});

program.parse(process.argv);
program.parse(args);

if (!process.argv.slice(2).length) {
if (!args.slice(2).length) {
program.outputHelp();
}
}

main();
if (require.main === module) {
main();
}
25 changes: 22 additions & 3 deletions src/util/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ export function pushChanges(): void {
}
}

export function setUserName(name: string): void {
execSync(`git config user.name "${name}"`);
}

export function setUserEmail(email: string): void {
execSync(`git config user.email "${email}"`);
}

export function setupUpstreamBranch(): void {
const branchName = getCurrentBranchName();
execSync(`git push --set-upstream origin ${branchName}`);
Expand Down Expand Up @@ -71,9 +79,20 @@ export function getStatusForFile(filePath: string): GitFileStatus {
}

export function getDiffForStagedFiles(): string {
var diff: string = execSync('git diff --staged').toString();
diff = removeDiffForFile(diff, 'package-lock.json');
return diff;
const maxBufferSize = 10 * 1024 * 1024; // 10 MB buffer
try {
let diff: string = execSync('git diff --staged', { maxBuffer: maxBufferSize }).toString();
diff = removeDiffForFile(diff, 'package-lock.json');
return diff;
} catch (error: any) {
if (error.code === 'ENOBUFS') {
throw new Error(
'Buffer overflow: The diff output exceeds the buffer limit. Consider reducing the number of staged files or increasing the buffer size.',
);
} else {
throw error; // Re-throw other errors if they are not ENOBUFS
}
}
}

function removeDiffForFile(diff: string, filePath: string): string {
Expand Down
53 changes: 53 additions & 0 deletions tests/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { main } from '../src/main';
import commit from '../src/cmd/commit';

jest.mock('../src/cmd/commit', () => jest.fn());

// Mock `process.exit` to prevent Jest from exiting during tests
const mockExit = jest.spyOn(process, 'exit').mockImplementation((code?: string | number | null | undefined) => {
throw new Error(`process.exit called with code ${code}`);
});

describe('Main CLI', () => {
const originalArgv = process.argv;

beforeEach(() => {
jest.clearAllMocks();
});

afterAll(() => {
process.argv = originalArgv;
mockExit.mockRestore();
});

test('should call commit command with default options', async () => {
process.argv = ['node', 'main.js', 'commit'];
await main();

expect(commit).toHaveBeenCalledWith('gpt-4o', undefined); // Default model, no `--all`
});

test('should call commit command with custom model and --all option', async () => {
process.argv = ['node', 'main.js', 'commit', '-m', 'gpt-3.5', '--all'];
await main();

expect(commit).toHaveBeenCalledWith('gpt-3.5', true);
});

test('should show help when no arguments are passed', async () => {
// Mock `program.outputHelp` directly
const outputHelpSpy = jest
.spyOn(require('commander').Command.prototype, 'outputHelp')
.mockImplementation(() => {});

process.argv = ['node', 'main.js'];
try {
await main();
} catch (e) {
expect((e as Error).message).toContain('process.exit called'); // Ensure process.exit was called
}

expect(outputHelpSpy).toHaveBeenCalled();
outputHelpSpy.mockRestore();
});
});
24 changes: 21 additions & 3 deletions tests/util/ai.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
import { createTextGeneration, setModel, getModel, validateModelName, listModelNames } from '../../src/util/ai';

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
// Mock `print` function from the prompt module and directly handle console.error and console.warn
jest.mock('../../src/util/prompt', () => ({
print: (type: string, message: string) => {
if (type === 'error') {
// Suppress console.error output
jest.fn();
} else if (type === 'warn') {
// Suppress console.warn output if necessary
jest.fn();
} else {
console.log(message); // Allow console.log for non-error types
}
},
}));

describe('AI Utility Integration Tests', () => {
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

beforeAll(() => {
// Ensure API key is present for the test to run
if (!OPENAI_API_KEY) {
throw new Error('OPENAI_API_KEY is not set in environment variables.');
}
setModel('gpt-4'); // or set your desired model version
setModel('gpt-4');
});

beforeEach(() => {
process.env.OPENAI_API_KEY = OPENAI_API_KEY;
});

afterAll(() => {
process.env.OPENAI_API_KEY = OPENAI_API_KEY;
});

test('should set model', async () => {
const model = 'gpt-4';
await setModel(model);
Expand Down
93 changes: 44 additions & 49 deletions tests/util/git.test.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,73 @@
import {
GitFileStatus,
stageAllFiles,
getCurrentBranchName,
getStatusForFile,
hasGitChanges,
isInGitRepo,
stageAllFiles,
commitWithMessage,
setUserEmail,
setUserName,
} from '../../src/util/git';
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

describe('Git Utilities Integration Tests', () => {
const testFilePath = 'test-file.txt';
let testRepoDir: string;

jest.mock('child_process', () => ({
execSync: jest.fn(),
}));
beforeAll(() => {
// Create a temporary directory for the test repository
testRepoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-repo-'));
process.chdir(testRepoDir);

// Ensure a Git repository is initialized and an initial commit is made
try {
execSync('git init');
// Set Git user name and email
setUserName('Test User');
setUserEmail('test@example.com');
fs.writeFileSync(testFilePath, 'Initial content');
stageAllFiles();
commitWithMessage('Initial commit');
} catch (error) {
console.error('Failed to initialize Git repository for tests.');
}
});

describe('Git Utilities', () => {
afterEach(() => {
jest.clearAllMocks();
afterAll(() => {
// Clean up by removing the test repository
process.chdir('..'); // Move out of the test repository directory
fs.rmSync(testRepoDir, { recursive: true, force: true });
});

describe('isInGitRepo', () => {
test('should return true if inside a Git repository', () => {
(execSync as jest.Mock).mockReturnValueOnce('true');
expect(isInGitRepo()).toBe(true);
});

test('should return false if not inside a Git repository', () => {
(execSync as jest.Mock).mockImplementationOnce(() => {
throw new Error();
});
expect(isInGitRepo()).toBe(false);
});
});

describe('getCurrentBranchName', () => {
test('should return the current branch name', () => {
(execSync as jest.Mock).mockReturnValueOnce('main\n');
expect(getCurrentBranchName()).toBe('main');
});

test('should throw an error if branch name cannot be retrieved', () => {
(execSync as jest.Mock).mockImplementationOnce(() => {
throw new Error();
});
expect(() => getCurrentBranchName()).toThrow('Unable to get current branch name.');
});
});

describe('hasGitChanges', () => {
test('should return true if there are changes in the Git working tree', () => {
(execSync as jest.Mock).mockReturnValueOnce('M src/util/git.ts\n');
describe('addAllChanges and hasGitChanges', () => {
test('should detect changes after adding a file', () => {
fs.writeFileSync(testFilePath, 'Test content');
expect(hasGitChanges()).toBe(true);
});

test('should return false if there are no changes in the Git working tree', () => {
(execSync as jest.Mock).mockReturnValueOnce('');
expect(hasGitChanges()).toBe(false);
});
});

describe('addAllChanges', () => {
test('should stage all changes', () => {
stageAllFiles();
expect(execSync).toHaveBeenCalledWith('git add .');
describe('getCurrentBranchName', () => {
test('should return the current branch name', () => {
const branchName = getCurrentBranchName();
expect(branchName === 'master' || branchName === 'main').toBe(true);
});
});

describe('getStatusForFile', () => {
test('should return the status for a specific file', () => {
(execSync as jest.Mock).mockReturnValueOnce('M src/util/git.ts\n');
expect(getStatusForFile('src/util/git.ts')).toBe('M');
});

test('should return Ignored status if the file is not tracked', () => {
(execSync as jest.Mock).mockReturnValueOnce('');
expect(getStatusForFile('src/util/git.ts')).toBe(GitFileStatus['!']);
// Step 1: Modify the file after the initial commit and check the status
fs.writeFileSync(testFilePath, 'Modified content');
stageAllFiles();
expect(getStatusForFile(testFilePath)).toBe('M');
});
});
});
Loading