Skip to content

refactor: consolidate duplicated methods into BaseGitService - #17

Merged
ClaudiaFang merged 3 commits into
masterfrom
claude/fix-sonarqube-duplication-2Rteo
Apr 26, 2026
Merged

ClaudiaFang merged 3 commits into
masterfrom
claude/fix-sonarqube-duplication-2Rteo

Conversation

@ClaudiaFang

Copy link
Copy Markdown
Member

Move encodeContent, decodeContent, getRepoGitignores, and handleFileNotFound
from GitHubService and GitLabService into BaseGitService to eliminate
code duplication flagged by SonarQube.

https://claude.ai/code/session_01BA3xCZgi5JX6Wz4xgF1Hwz

claude added 3 commits April 26, 2026 02:32
Move encodeContent, decodeContent, getRepoGitignores, and handleFileNotFound
from GitHubService and GitLabService into BaseGitService to eliminate
code duplication flagged by SonarQube.

https://claude.ai/code/session_01BA3xCZgi5JX6Wz4xgF1Hwz
Sync package-lock.json with current package.json (name, version, license).

https://claude.ai/code/session_01BA3xCZgi5JX6Wz4xgF1Hwz
Cover listFiles, deleteFile, testConnection, getRepoGitignores in both
GitHubService and GitLabService. Add BaseGitService tests for non-Error
safeRequest throws and rootPath-ends-with-slash branch. Add gitignore-manager
error fallback tests and sync-manager batch onProgress/rename tests.

Improves statement coverage from 78.6% to 92.6% and function coverage
from 69.1% to 94.5%.

https://claude.ai/code/session_01BA3xCZgi5JX6Wz4xgF1Hwz
@sonarqubecloud

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Git service layer by moving shared logic for content encoding, decoding, and error handling into the BaseGitService class, reducing duplication across GitHub and GitLab implementations. It also introduces new tests for GitignoreManager fallbacks and SyncManager batch operations. Feedback highlights a logic error in getRepoGitignores where rootPath filtering might exclude necessary ignore files, and suggests performance and semantic improvements for content processing methods. Additionally, it is recommended to separate project metadata and license changes from the primary refactoring effort.

Comment on lines +120 to +123
async getRepoGitignores(branch: string): Promise<string[]> {
const allFiles = await this.listFiles(branch);
return allFiles.filter(p => p.endsWith('.gitignore'));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The implementation of getRepoGitignores has two issues:

  1. Logic Error with rootPath: It calls this.listFiles(branch), which in subclasses (GitHubService, GitLabService) filters results to only include files within the rootPath. This means .gitignore files at the repository root or in parent directories of the rootPath will be missed, even though they correctly apply to files within the vault. This breaks the ignore logic for subdirectory-based vaults.
  2. Filter Precision: p.endsWith('.gitignore') will match files like not-a.gitignore. Standard gitignore files should be named exactly .gitignore or be located in a subdirectory as /.gitignore.

Consider implementing a way to list all files in the repository without the rootPath filter for this specific method.

Suggested change
async getRepoGitignores(branch: string): Promise<string[]> {
const allFiles = await this.listFiles(branch);
return allFiles.filter(p => p.endsWith('.gitignore'));
}
async getRepoGitignores(branch: string): Promise<string[]> {
// Note: subclasses currently filter listFiles by rootPath, which may miss global gitignores
const allFiles = await this.listFiles(branch);
return allFiles.filter(p => p === '.gitignore' || p.endsWith('/.gitignore'));
}

Comment thread package-lock.json
Comment on lines +2 to +10
"name": "git-file-sync",
"version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "git-file-push",
"version": "1.0.0",
"license": "0-BSD",
"name": "git-file-sync",
"version": "1.1.0",
"license": "MIT",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This pull request includes significant metadata changes in package-lock.json, such as renaming the project from git-file-push to git-file-sync, bumping the version to 1.1.0, and changing the license from 0-BSD to MIT. These changes are unrelated to the stated goal of refactoring duplicated methods into BaseGitService. It is best practice to keep metadata and licensing changes in separate, dedicated commits or pull requests to maintain a clear audit trail.

Comment on lines +91 to +101
protected encodeContent(content: string): string {
const bytes = new TextEncoder().encode(content);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
const byte = bytes[i];
if (byte !== undefined) {
binary += String.fromCodePoint(byte);
}
}
return btoa(binary);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of encodeContent uses a loop with string concatenation for every byte. This is inefficient for large files as it leads to repeated string allocations and memory pressure. A more performant approach is to convert the Uint8Array to a binary string using Array.from with a mapping function before joining, which is better optimized in modern JavaScript engines.

    protected encodeContent(content: string): string {
        const bytes = new TextEncoder().encode(content);
        const binary = Array.from(bytes, b => String.fromCharCode(b)).join('');
        return btoa(binary);
    }

Comment on lines +103 to +111
protected decodeContent(base64: string): string {
const binary = atob(base64.replace(/\s/g, ''));
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
const cp = binary.codePointAt(i);
bytes[i] = cp !== undefined ? cp : 0;
}
return new TextDecoder().decode(bytes);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In decodeContent, using binary.codePointAt(i) is semantically misleading because atob returns a string where each character represents a single byte (0-255). charCodeAt(i) is more appropriate. Additionally, the loop can be replaced with the more idiomatic Uint8Array.from to improve readability and efficiency.

    protected decodeContent(base64: string): string {
        const binary = atob(base64.replace(/\s/g, ''));
        const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
        return new TextDecoder().decode(bytes);
    }

@ClaudiaFang
ClaudiaFang merged commit 591dd6e into master Apr 26, 2026
18 checks passed
@ClaudiaFang
ClaudiaFang deleted the claude/fix-sonarqube-duplication-2Rteo branch April 26, 2026 02:46
@ClaudiaFang

Copy link
Copy Markdown
Member Author

🎉 This PR is included in version 1.0.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants