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

Feat/pre commit #449

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
✨ Add logging and caching configuration options to enhance debugging …
…and performance

The commit introduces new configuration options for logging and caching in OpenCommit. Users can now enable/disable logging and caching, and customize the directories for storing logs and cache files. This improves debugging capabilities, performance optimization, and pre-commit hook integration. The changes include updates to the README.md documentation, configuration handling in config.ts, and enhancements to the commit cache and logger utilities.
  • Loading branch information
majiayu000 committed Feb 12, 2025
commit 5f89cdc3ecc0ff7788d5c835f25c234fea167ed1
54 changes: 52 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -118,6 +118,10 @@ OCO_LANGUAGE=<locale, scroll to the bottom to see options>
OCO_MESSAGE_TEMPLATE_PLACEHOLDER=<message template placeholder, default: '$msg'>
OCO_PROMPT_MODULE=<either conventional-commit or @commitlint, default: conventional-commit>
OCO_ONE_LINE_COMMIT=<one line commit message, default: false>
OCO_ENABLE_LOGGING=<boolean, enable/disable logging, default: true>
OCO_ENABLE_CACHE=<boolean, enable/disable commit message caching, default: true>
OCO_CACHE_DIR=<custom directory for storing commit message cache>
OCO_LOG_DIR=<custom directory for storing log files>
```

Global configs are same as local configs, but they are stored in the global `~/.opencommit` config file and set with `oco config set` command, e.g. `oco config set OCO_MODEL=gpt-4o`.
@@ -205,6 +209,52 @@ oco config set OCO_LANGUAGE=française
The default language setting is **English**
All available languages are currently listed in the [i18n](https://github.com/di-sukharev/opencommit/tree/master/src/i18n) folder

### Logging and Caching Configuration

OpenCommit provides options to control logging and caching behavior. These features help with debugging, performance optimization, and pre-commit hook integration.

#### Logging Configuration

You can enable/disable logging and customize the log directory:

```sh
# Disable logging
oco config set OCO_ENABLE_LOGGING=false

# Set custom log directory
oco config set OCO_LOG_DIR=/path/to/custom/logs
```

By default:
- Logs are stored in `~/.cache/opencommit/logs/` directory
- Logs include commit generation process, errors, and pre-commit hook results
- Each log entry is timestamped and categorized by level (INFO, ERROR, etc.)

#### Caching Configuration

OpenCommit caches commit messages to improve performance and provide better suggestions, especially useful during pre-commit hook operations:

```sh
# Disable commit message caching
oco config set OCO_ENABLE_CACHE=false

# Set custom cache directory
oco config set OCO_CACHE_DIR=/path/to/custom/cache
```

By default:
- Caching is enabled
- Cache files are stored in `~/.cache/opencommit/` directory
- Cache entries expire after 7 days
- Each repository has its own cache file
- Cache includes successful commit messages and their associated file changes
- Cache is automatically cleared after successful commits
- Cache helps speed up pre-commit hook operations by reusing recent commit messages

The cache system is cross-platform compatible and follows XDG Base Directory specifications on Linux systems.

> Note: When using pre-commit hooks, logging and caching are particularly useful for debugging issues and improving performance. The log files contain detailed information about hook execution and any failures that might occur.

### Push to git (gonna be deprecated)

A prompt for pushing to git is on by default but if you would like to turn it off just use:
@@ -280,7 +330,7 @@ git commit -m "${generatedMessage}" --no-verify
To include a message in the generated message, you can utilize the template function, for instance:

```sh
oco '#205: $msg
oco '#205: $msg'
```

> opencommit examines placeholders in the parameters, allowing you to append additional information before and after the placeholders, such as the relevant Issue or Pull Request. Similarly, you have the option to customize the OCO_MESSAGE_TEMPLATE_PLACEHOLDER configuration item, for example, simplifying it to $m!"
@@ -306,7 +356,7 @@ This line is responsible for replacing the placeholder in the `messageTemplate`

#### Usage

For instance, using the command `oco '$msg #205`, users can leverage this feature. The provided code represents the backend mechanics of such commands, ensuring that the placeholder is replaced with the appropriate commit message.
For instance, using the command `oco '$msg #205'`, users can leverage this feature. The provided code represents the backend mechanics of such commands, ensuring that the placeholder is replaced with the appropriate commit message.

#### Committing with the Message

22 changes: 19 additions & 3 deletions src/commands/config.ts
Original file line number Diff line number Diff line change
@@ -25,7 +25,11 @@ export enum CONFIG_KEYS {
OCO_ONE_LINE_COMMIT = 'OCO_ONE_LINE_COMMIT',
OCO_TEST_MOCK_TYPE = 'OCO_TEST_MOCK_TYPE',
OCO_API_URL = 'OCO_API_URL',
OCO_GITPUSH = 'OCO_GITPUSH' // todo: deprecate
OCO_GITPUSH = 'OCO_GITPUSH',
OCO_ENABLE_LOGGING = 'OCO_ENABLE_LOGGING',
OCO_ENABLE_CACHE = 'OCO_ENABLE_CACHE',
OCO_CACHE_DIR = 'OCO_CACHE_DIR',
OCO_LOG_DIR = 'OCO_LOG_DIR'
}

export enum CONFIG_MODES {
@@ -383,6 +387,10 @@ export type ConfigType = {
[CONFIG_KEYS.OCO_GITPUSH]: boolean;
[CONFIG_KEYS.OCO_ONE_LINE_COMMIT]: boolean;
[CONFIG_KEYS.OCO_TEST_MOCK_TYPE]: string;
[CONFIG_KEYS.OCO_ENABLE_LOGGING]?: boolean;
[CONFIG_KEYS.OCO_ENABLE_CACHE]?: boolean;
[CONFIG_KEYS.OCO_CACHE_DIR]?: string;
[CONFIG_KEYS.OCO_LOG_DIR]?: string;
};

export const defaultConfigPath = pathJoin(homedir(), '.opencommit');
@@ -429,7 +437,11 @@ export const DEFAULT_CONFIG = {
OCO_ONE_LINE_COMMIT: false,
OCO_TEST_MOCK_TYPE: 'commit-message',
OCO_WHY: false,
OCO_GITPUSH: true // todo: deprecate
OCO_GITPUSH: true,
OCO_ENABLE_LOGGING: true,
OCO_ENABLE_CACHE: true,
OCO_CACHE_DIR: '',
OCO_LOG_DIR: ''
};

const initGlobalConfig = (configPath: string = defaultConfigPath) => {
@@ -468,7 +480,11 @@ const getEnvConfig = (envPath: string) => {
OCO_ONE_LINE_COMMIT: parseConfigVarValue(process.env.OCO_ONE_LINE_COMMIT),
OCO_TEST_MOCK_TYPE: process.env.OCO_TEST_MOCK_TYPE,

OCO_GITPUSH: parseConfigVarValue(process.env.OCO_GITPUSH) // todo: deprecate
OCO_GITPUSH: parseConfigVarValue(process.env.OCO_GITPUSH),
OCO_ENABLE_LOGGING: parseConfigVarValue(process.env.OCO_ENABLE_LOGGING),
OCO_ENABLE_CACHE: parseConfigVarValue(process.env.OCO_ENABLE_CACHE),
OCO_CACHE_DIR: parseConfigVarValue(process.env.OCO_CACHE_DIR),
OCO_LOG_DIR: parseConfigVarValue(process.env.OCO_LOG_DIR)
};
};

44 changes: 31 additions & 13 deletions src/utils/commitCache.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { join } from 'path';
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
import { execa } from 'execa';
import { createHash } from 'crypto';
import { getConfig } from '../commands/config';

export interface CommitCacheData {
message: string;
@@ -12,8 +13,18 @@ export interface CommitCacheData {
}

export class CommitCache {
private static readonly XDG_CACHE_HOME = process.env.XDG_CACHE_HOME || join(homedir(), '.cache');
private static readonly CACHE_DIR = join(CommitCache.XDG_CACHE_HOME, 'opencommit');
private static readonly DEFAULT_CACHE_HOME = process.env.XDG_CACHE_HOME || join(homedir(), '.cache');
private static readonly DEFAULT_CACHE_DIR = join(CommitCache.DEFAULT_CACHE_HOME, 'opencommit');

private static getCacheDir(): string {
const config = getConfig();
return config.OCO_CACHE_DIR || CommitCache.DEFAULT_CACHE_DIR;
}

private static isCacheEnabled(): boolean {
const config = getConfig();
return config.OCO_ENABLE_CACHE !== false;
}

private static async getRepoRoot(): Promise<string> {
try {
@@ -26,12 +37,15 @@ export class CommitCache {

private static getCacheFilePath(repoPath: string): string {
const repoHash = createHash('md5').update(repoPath).digest('hex');
return join(this.CACHE_DIR, `${repoHash}_commit_cache.json`);
return join(this.getCacheDir(), `${repoHash}_commit_cache.json`);
}

private static ensureCacheDir() {
if (!existsSync(this.CACHE_DIR)) {
mkdirSync(this.CACHE_DIR, { recursive: true });
if (!this.isCacheEnabled()) return;

const cacheDir = this.getCacheDir();
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true });
}
}

@@ -46,6 +60,8 @@ export class CommitCache {
}

static async saveCommitMessage(message: string) {
if (!this.isCacheEnabled()) return;

this.ensureCacheDir();
const repoPath = await this.getRepoRoot();
const files = await this.getStagedFiles();
@@ -60,6 +76,8 @@ export class CommitCache {
}

static async getLastCommitMessage(): Promise<CommitCacheData | null> {
if (!this.isCacheEnabled()) return null;

try {
const repoPath = await this.getRepoRoot();
const cacheFilePath = this.getCacheFilePath(repoPath);
@@ -75,19 +93,14 @@ export class CommitCache {

const cacheData = JSON.parse(cacheContent) as CommitCacheData;

// Verify if the cache is for the current repository
if (cacheData.repoPath !== repoPath) {
return null;
}

// Get current staged files
const currentFiles = await this.getStagedFiles();

// Compare file lists (order doesn't matter)
const cachedFileSet = new Set(cacheData.files);
const currentFileSet = new Set(currentFiles);

// Check if the file sets are equal
if (cachedFileSet.size !== currentFileSet.size) {
return null;
}
@@ -106,6 +119,8 @@ export class CommitCache {
}

static async clearCache() {
if (!this.isCacheEnabled()) return;

try {
const repoPath = await this.getRepoRoot();
const cacheFilePath = this.getCacheFilePath(repoPath);
@@ -118,19 +133,22 @@ export class CommitCache {
}

static async cleanupOldCaches() {
if (!this.isCacheEnabled()) return;

try {
if (!existsSync(this.CACHE_DIR)) {
const cacheDir = this.getCacheDir();
if (!existsSync(cacheDir)) {
return;
}

const files = readdirSync(this.CACHE_DIR);
const files = readdirSync(cacheDir);
const now = Date.now();
const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days

for (const file of files) {
if (!file.endsWith('_commit_cache.json')) continue;

const filePath = join(this.CACHE_DIR, file);
const filePath = join(cacheDir, file);
try {
const content = readFileSync(filePath, 'utf-8');
if (!content.trim()) continue;
32 changes: 26 additions & 6 deletions src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -3,14 +3,32 @@ import { join } from 'path';
import { existsSync, mkdirSync, appendFileSync } from 'fs';
import { format } from 'util';
import chalk from 'chalk';
import { getConfig } from '../commands/config';

export class Logger {
private static readonly LOG_DIR = join(homedir(), '.cache', 'opencommit', 'logs');
private static readonly LOG_FILE = join(Logger.LOG_DIR, 'opencommit.log');
private static readonly DEFAULT_LOG_DIR = join(homedir(), '.cache', 'opencommit', 'logs');
private static readonly DEFAULT_LOG_FILE = join(Logger.DEFAULT_LOG_DIR, 'opencommit.log');

private static getLogDir(): string {
const config = getConfig();
return config.OCO_LOG_DIR || Logger.DEFAULT_LOG_DIR;
}

private static getLogFile(): string {
return join(this.getLogDir(), 'opencommit.log');
}

private static isLoggingEnabled(): boolean {
const config = getConfig();
return config.OCO_ENABLE_LOGGING !== false;
}

private static ensureLogDir() {
if (!existsSync(Logger.LOG_DIR)) {
mkdirSync(Logger.LOG_DIR, { recursive: true });
if (!this.isLoggingEnabled()) return;

const logDir = this.getLogDir();
if (!existsSync(logDir)) {
mkdirSync(logDir, { recursive: true });
}
}

@@ -24,11 +42,13 @@ export class Logger {
}

private static writeToLog(level: string, message: string) {
if (!this.isLoggingEnabled()) return;

this.ensureLogDir();
const timestamp = this.getTimestamp();
const cleanMessage = this.stripAnsi(message);
const logEntry = `[${timestamp}] [${level}] ${cleanMessage}\n`;
appendFileSync(this.LOG_FILE, logEntry);
appendFileSync(this.getLogFile(), logEntry);
}

private static formatMultilineMessage(message: string): string {
@@ -94,6 +114,6 @@ export class Logger {
}

static getLogPath(): string {
return this.LOG_FILE;
return this.isLoggingEnabled() ? this.getLogFile() : 'Logging is disabled';
}
}
Loading
Oops, something went wrong.