Skip to content

Commit

Permalink
refactor: improve error handling and telemetry
Browse files Browse the repository at this point in the history
- Refactor error handling in API calls
- Improve telemetry event tracking
- Add retry logic for API calls
- Enhance logging and error messages
  • Loading branch information
VizzleTF committed Jan 20, 2025
1 parent 127055e commit 7de8993
Show file tree
Hide file tree
Showing 14 changed files with 629 additions and 478 deletions.
1 change: 0 additions & 1 deletion src/constants.ts

This file was deleted.

5 changes: 2 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import * as vscode from 'vscode';
import { GitService } from './services/gitService';
import { Logger } from './utils/logger';
import { ConfigService } from './utils/configService';
import { generateAndSetCommitMessage } from './services/aiService';
import { CommitMessageUI } from './services/aiService';
import { SettingsValidator } from './services/settingsValidator';
import { TelemetryService } from './services/telemetryService';
import { messages } from './utils/constants';

export async function activate(context: vscode.ExtensionContext): Promise<void> {
void Logger.log('Starting extension activation');
Expand All @@ -29,7 +28,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
context.subscriptions.push(
vscode.commands.registerCommand('geminicommit.generateCommitMessage', async () => {
try {
await generateAndSetCommitMessage();
await CommitMessageUI.generateAndSetCommitMessage();
} catch (error) {
void Logger.error('Error in generateCommitMessage command:', error as Error);
void vscode.window.showErrorMessage(`Error: ${(error as Error).message}`);
Expand Down
364 changes: 177 additions & 187 deletions src/services/aiService.ts

Large diffs are not rendered by default.

21 changes: 4 additions & 17 deletions src/services/customEndpointService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ interface CustomApiResponse {
}>;
}

interface ApiHeaders {
contentType: string;
authBearer: string;
}
type ApiHeaders = Record<string, string>;

export class CustomEndpointService {
static async generateCommitMessage(
Expand All @@ -31,25 +28,15 @@ export class CustomEndpointService {
};

const headers: ApiHeaders = {
contentType: 'application/json',
authBearer: `Bearer ${apiKey}`
'content-type': 'application/json',
'authorization': `Bearer ${apiKey}`
};

try {
void Logger.log('Sending request to custom endpoint');
progress.report({ message: 'Generating commit message...', increment: 50 });

const requestHeaders = {
contentType: headers.contentType,
authorization: headers.authBearer
};

const response = await axios.post<CustomApiResponse>(endpoint, payload, {
headers: {
'content-type': requestHeaders.contentType,
'authorization': requestHeaders.authorization
}
});
const response = await axios.post<CustomApiResponse>(endpoint, payload, { headers });

void Logger.log('Custom endpoint response received successfully');
progress.report({ message: 'Commit message generated successfully', increment: 100 });
Expand Down
151 changes: 84 additions & 67 deletions src/services/gitBlameAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as vscode from 'vscode';
import { spawn } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
import { Logger } from '../utils/logger';
import { errorMessages } from '../utils/constants';

interface BlameInfo {
commit: string;
Expand All @@ -10,95 +12,125 @@ interface BlameInfo {
line: string;
}

interface GitProcessResult<T> {
data: T;
stderr: string[];
}

export class GitBlameAnalyzer {
private static async getGitBlame(filePath: string): Promise<BlameInfo[]> {
return new Promise((resolve, reject) => {
if (!fs.existsSync(filePath)) {
reject(new Error(`File does not exist: ${filePath}`));
return;
}
private static async executeGitCommand<T>(
command: string[],
filePath: string,
processOutput: (data: Buffer) => T,
ignoreFileNotFound = false
): Promise<GitProcessResult<T>> {
if (!ignoreFileNotFound && !fs.existsSync(filePath)) {
throw new Error(`${errorMessages.fileNotFound}: ${filePath}`);
}

const blame: BlameInfo[] = [];
const gitProcess = spawn('git', ['blame', '--line-porcelain', path.basename(filePath)], {
return new Promise((resolve, reject) => {
const gitProcess = spawn('git', command, {
cwd: path.dirname(filePath)
});

let currentBlame: Partial<BlameInfo> = {};
let result: T;
const stderr: string[] = [];

gitProcess.stdout.on('data', (data: Buffer) => {
const lines = data.toString().split('\n');
lines.forEach((line: string) => {
if (line.startsWith('author ')) {
currentBlame.author = line.substring(7);
} else if (line.startsWith('committer-time ')) {
currentBlame.date = new Date(parseInt(line.substring(15)) * 1000).toISOString();
} else if (line.startsWith('\t')) {
currentBlame.line = line.substring(1);
blame.push(currentBlame as BlameInfo);
currentBlame = {};
} else if (line.match(/^[0-9a-f]{40}/)) {
currentBlame.commit = line.split(' ')[0];
}
});
result = processOutput(data);
});

gitProcess.stderr.on('data', (data: Buffer) => {
console.error(`Git blame stderr: ${data.toString()}`);
const errorMessage = data.toString();
stderr.push(errorMessage);
void Logger.error(`Git command stderr: ${errorMessage}`);
});

gitProcess.on('close', (code) => {
if (code === 0) {
resolve(blame);
if (code === 0 || (ignoreFileNotFound && code === 128)) {
resolve({ data: result, stderr });
} else {
reject(new Error(`Git blame process exited with code ${code}`));
reject(new Error(`Git process exited with code ${code}`));
}
});
});
}

private static async getDiff(repoPath: string, filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
const gitProcess = spawn('git', ['diff', '--', path.basename(filePath)], { cwd: path.dirname(filePath) });
let diff = '';

gitProcess.stdout.on('data', (data: Buffer) => {
diff += data.toString();
});
private static async getGitBlame(filePath: string): Promise<BlameInfo[]> {
if (!fs.existsSync(filePath)) {
void Logger.log(`Skipping blame for non-existent file: ${filePath}`);
return [];
}

gitProcess.stderr.on('data', (data: Buffer) => {
console.error(`Git diff stderr: ${data.toString()}`);
});
const processBlameOutput = (data: Buffer): BlameInfo[] => {
const blame: BlameInfo[] = [];
let currentBlame: Partial<BlameInfo> = {};

gitProcess.on('close', (code) => {
if (code === 0) {
resolve(diff);
} else {
reject(new Error(`Git diff process exited with code ${code}`));
const lines = data.toString().split('\n');
lines.forEach((line: string) => {
if (line.startsWith('author ')) {
currentBlame.author = line.substring(7);
} else if (line.startsWith('committer-time ')) {
currentBlame.date = new Date(parseInt(line.substring(15)) * 1000).toISOString();
} else if (line.startsWith('\t')) {
currentBlame.line = line.substring(1);
blame.push(currentBlame as BlameInfo);
currentBlame = {};
} else if (line.match(/^[0-9a-f]{40}/)) {
currentBlame.commit = line.split(' ')[0];
}
});
});

return blame;
};

const { data } = await this.executeGitCommand(
['blame', '--line-porcelain', path.basename(filePath)],
filePath,
processBlameOutput
);

return data;
}

static async analyzeChanges(repoPath: string, filePath: string): Promise<string> {
private static async getDiff(repoPath: string, filePath: string): Promise<string> {
const processDiffOutput = (data: Buffer): string => data.toString();

try {
console.log(`Analyzing changes for file: ${filePath}`);
const { data } = await this.executeGitCommand(
['diff', '--', path.basename(filePath)],
filePath,
processDiffOutput,
true // Ignore if file doesn't exist (for moved/deleted files)
);

return data;
} catch (error) {
void Logger.log(`Could not get diff for ${filePath}, might be moved/deleted`);
return '';
}
}

if (!fs.existsSync(filePath)) {
throw new Error(`File does not exist: ${filePath}`);
}
static async analyzeChanges(repoPath: string, filePath: string): Promise<string> {
try {
void Logger.log(`Analyzing changes for file: ${filePath}`);

const blame = await this.getGitBlame(filePath);
console.log(`Git blame completed for ${filePath}`);
void Logger.log(`Git blame completed for ${filePath}`);

const diff = await this.getDiff(repoPath, filePath);
console.log(`Git diff completed for ${filePath}`);
void Logger.log(`Git diff completed for ${filePath}`);

if (!blame.length && !diff) {
return `File ${path.basename(filePath)} was moved or deleted`;
}

const changedLines = this.parseChangedLines(diff);
const blameAnalysis = this.analyzeBlameInfo(blame, changedLines);

return this.formatAnalysis(blameAnalysis);
} catch (error) {
console.error('Error in GitBlameAnalyzer:', error);
void Logger.error('Error in GitBlameAnalyzer:', error as Error);
throw error;
}
}
Expand Down Expand Up @@ -152,19 +184,4 @@ export class GitBlameAnalyzer {

return result;
}
}

// Usage in the extension
export async function analyzeFileChanges(filePath: string): Promise<string> {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath));
if (!workspaceFolder) {
throw new Error('File is not part of a workspace');
}

try {
return await GitBlameAnalyzer.analyzeChanges(workspaceFolder.uri.fsPath, filePath);
} catch (error) {
console.error(`Error analyzing file changes for ${filePath}:`, error);
return `Unable to analyze changes for ${filePath}: ${(error as Error).message}`;
}
}
Loading

0 comments on commit 7de8993

Please sign in to comment.