Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff0bc72408 | |||
| 734137e7b5 | |||
| 238dd152ba | |||
| 9bacb2e548 |
18
changelog.md
18
changelog.md
@@ -1,5 +1,23 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-11-04 - 3.3.1 - fix(getUncommittedDiff)
|
||||||
|
Avoid false-positive diffs in getUncommittedDiff by detecting symlinked directories and skipping identical files
|
||||||
|
|
||||||
|
- Detect files reported as "added" that are actually inside symlinked directories (catch isomorphic-git error: "anticipated to be a tree but it is a blob") and skip them to avoid huge false-positive lists.
|
||||||
|
- Compare HEAD and workdir file contents and skip entries where contents are identical to filter out permission/timestamp/line-ending false positives.
|
||||||
|
- Add glob support for excludeFiles via minimatch and skip exact or glob-matching paths during diff collection.
|
||||||
|
- Files changed: ts/smartgit.classes.gitrepo.ts (symlink detection, content comparison, diff filtering), ts/smartgit.plugins.ts (export minimatch), readme.hints.md (notes).
|
||||||
|
- Observed impact: false positives reduced dramatically in reported case (1,883 → 2 files); output size reduced from ~59 MB → ~2 KB.
|
||||||
|
|
||||||
|
## 2025-11-04 - 3.3.0 - feat(GitRepo)
|
||||||
|
Add glob-pattern exclusions for getUncommittedDiff and add minimatch; bump dependencies
|
||||||
|
|
||||||
|
- getUncommittedDiff now supports glob patterns for excluded files via minimatch (skip files when filepath matches exact or glob pattern).
|
||||||
|
- Expose minimatch through plugins (ts/smartgit.plugins.ts) so plugin code can use glob matching consistently.
|
||||||
|
- Add minimatch to dependencies (minimatch ^10.1.1).
|
||||||
|
- Bump several dependencies: @push.rocks/smartenv to ^6.0.0, @push.rocks/smartfile to ^11.2.7, @push.rocks/smartshell to ^3.3.0, @push.rocks/smartstring to ^4.1.0, isomorphic-git to ^1.34.2.
|
||||||
|
- Bump devDependencies for build/test tooling: @git.zone/tsbuild ^2.7.1, @git.zone/tsrun ^1.6.2, @git.zone/tstest ^2.7.0.
|
||||||
|
|
||||||
## 2025-01-04 - 3.2.0 - feat(core)
|
## 2025-01-04 - 3.2.0 - feat(core)
|
||||||
Enhanced error handling, type safety, and documentation
|
Enhanced error handling, type safety, and documentation
|
||||||
|
|
||||||
|
|||||||
19
package.json
19
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartgit",
|
"name": "@push.rocks/smartgit",
|
||||||
"version": "3.2.1",
|
"version": "3.3.1",
|
||||||
"description": "A smart wrapper for nodegit that simplifies Git operations in Node.js.",
|
"description": "A smart wrapper for nodegit that simplifies Git operations in Node.js.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
@@ -32,21 +32,22 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://code.foss.global/push.rocks/smartgit",
|
"homepage": "https://code.foss.global/push.rocks/smartgit",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/smartenv": "^5.0.13",
|
"@push.rocks/smartenv": "^6.0.0",
|
||||||
"@push.rocks/smartfile": "^11.2.5",
|
"@push.rocks/smartfile": "^11.2.7",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartshell": "^3.2.3",
|
"@push.rocks/smartshell": "^3.3.0",
|
||||||
"@push.rocks/smartstring": "^4.0.15",
|
"@push.rocks/smartstring": "^4.1.0",
|
||||||
"@push.rocks/smarttime": "^4.1.1",
|
"@push.rocks/smarttime": "^4.1.1",
|
||||||
"@types/diff": "^8.0.0",
|
"@types/diff": "^8.0.0",
|
||||||
"diff": "^8.0.2",
|
"diff": "^8.0.2",
|
||||||
"isomorphic-git": "^1.32.2"
|
"isomorphic-git": "^1.34.2",
|
||||||
|
"minimatch": "^10.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.6.4",
|
"@git.zone/tsbuild": "^2.7.1",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@git.zone/tsrun": "^1.6.2",
|
||||||
"@git.zone/tstest": "^2.3.2"
|
"@git.zone/tstest": "^2.7.0"
|
||||||
},
|
},
|
||||||
"private": false,
|
"private": false,
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
4939
pnpm-lock.yaml
generated
4939
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +1,32 @@
|
|||||||
|
# smartgit Project Hints
|
||||||
|
|
||||||
|
## Recent Fixes
|
||||||
|
|
||||||
|
### getUncommittedDiff() False Positives Fix (2025-11-04)
|
||||||
|
|
||||||
|
**Problem**:
|
||||||
|
- Method was reporting 1,883 diffs when only 1-2 files were actually modified
|
||||||
|
- Root cause: isomorphic-git's `statusMatrix()` reports files inside symlinked directories as "added" files
|
||||||
|
- Example: `ghost_local/current` → symlink to `ghost_local/versions/5.129.1` causes all 1,880+ files inside to be reported as changes
|
||||||
|
|
||||||
|
**Solution Implemented**:
|
||||||
|
1. **Symlink detection** (lines 160-184): For files reported as "added" (head=0, workdir≠0), try to read from HEAD anyway. If we get error "anticipated to be a tree but it is a blob", the parent path is a symlink - skip the file entirely.
|
||||||
|
|
||||||
|
2. **Content comparison** (lines 196-200): Before creating any diff, check if `headContent === workdirContent`. If identical, skip (catches permission/timestamp/line-ending false positives).
|
||||||
|
|
||||||
|
**Results**:
|
||||||
|
- Reduced false positives from 1,883 → 2 files (99.89% reduction)
|
||||||
|
- Output size: 59 MB → 2 KB (29,500x reduction)
|
||||||
|
- Only reports actual content changes
|
||||||
|
|
||||||
|
**Files Modified**:
|
||||||
|
- `ts/smartgit.classes.gitrepo.ts` lines 153-209
|
||||||
|
|
||||||
|
**Dependencies Added**:
|
||||||
|
- `minimatch` for glob pattern support in excludeFiles parameter
|
||||||
|
|
||||||
|
## Architecture Notes
|
||||||
|
|
||||||
|
- Main class: `Smartgit` (not `SmartGit` - lowercase 'g')
|
||||||
|
- Must call `await smartgit.init()` before use
|
||||||
|
- Repository methods: `createRepoByOpen()`, `createRepoByClone()`, `createRepoByInit()`
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartgit',
|
name: '@push.rocks/smartgit',
|
||||||
version: '3.1.1',
|
version: '3.3.1',
|
||||||
description: 'A smart wrapper for nodegit that simplifies Git operations in Node.js.'
|
description: 'A smart wrapper for nodegit that simplifies Git operations in Node.js.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ export class GitRepo {
|
|||||||
const diffs: string[] = [];
|
const diffs: string[] = [];
|
||||||
for (const row of statusMatrix) {
|
for (const row of statusMatrix) {
|
||||||
const [filepath, head, workdir] = row;
|
const [filepath, head, workdir] = row;
|
||||||
if (excludeFiles.includes(filepath)) {
|
if (excludeFiles.some(pattern => filepath === pattern || plugins.minimatch(filepath, pattern))) {
|
||||||
continue; // Skip excluded files
|
continue; // Skip excluded files (supports exact matches and glob patterns)
|
||||||
}
|
}
|
||||||
|
|
||||||
let headContent = '';
|
let headContent = '';
|
||||||
@@ -156,6 +156,31 @@ export class GitRepo {
|
|||||||
plugins.path.join(this.repoDir, filepath),
|
plugins.path.join(this.repoDir, filepath),
|
||||||
'utf8'
|
'utf8'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Try to read from HEAD anyway - catches false positives from symlinks
|
||||||
|
// where isomorphic-git reports symlink contents as "added" files
|
||||||
|
try {
|
||||||
|
headContent = await plugins.isomorphicGit
|
||||||
|
.readBlob({
|
||||||
|
fs: this.smartgitRef.envDeps.fs,
|
||||||
|
dir: this.repoDir,
|
||||||
|
oid: await plugins.isomorphicGit.resolveRef({
|
||||||
|
fs: this.smartgitRef.envDeps.fs,
|
||||||
|
dir: this.repoDir,
|
||||||
|
ref: 'HEAD',
|
||||||
|
}),
|
||||||
|
filepath,
|
||||||
|
})
|
||||||
|
.then((result) => new TextDecoder().decode(result.blob));
|
||||||
|
} catch (err) {
|
||||||
|
// Check if this is a symlink false positive
|
||||||
|
// Error: "was anticipated to be a tree but it is a blob" means parent path is a symlink
|
||||||
|
if (err.message && err.message.includes('anticipated to be a tree but it is a blob')) {
|
||||||
|
// This file is inside a symlinked directory - skip it entirely
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Otherwise, file truly doesn't exist in HEAD - leave headContent empty for diff
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle deleted files
|
// Handle deleted files
|
||||||
@@ -175,6 +200,11 @@ export class GitRepo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (headContent || workdirContent) {
|
if (headContent || workdirContent) {
|
||||||
|
// Skip files with identical content (filters false positives from statusMatrix)
|
||||||
|
if (headContent === workdirContent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const diff = plugins.diff.createTwoFilesPatch(
|
const diff = plugins.diff.createTwoFilesPatch(
|
||||||
filepath,
|
filepath,
|
||||||
filepath,
|
filepath,
|
||||||
|
|||||||
@@ -15,5 +15,6 @@ export { smartenv, smartfile, smartpath, smartpromise, smartstring, smarttime };
|
|||||||
// third party
|
// third party
|
||||||
import * as diff from 'diff';
|
import * as diff from 'diff';
|
||||||
import isomorphicGit from 'isomorphic-git';
|
import isomorphicGit from 'isomorphic-git';
|
||||||
|
import { minimatch } from 'minimatch';
|
||||||
|
|
||||||
export { diff, isomorphicGit };
|
export { diff, isomorphicGit, minimatch };
|
||||||
|
|||||||
Reference in New Issue
Block a user