Compare commits

...

42 Commits

Author SHA1 Message Date
jkunz 66cf31d50d v3.3.2 2026-04-30 18:19:27 +00:00
jkunz 8ed7413e62 fix(build): modernize project configuration and tighten Node.js typing support 2026-04-30 18:19:27 +00:00
jkunz ff0bc72408 3.3.1 2025-11-04 03:40:49 +00:00
jkunz 734137e7b5 fix(getUncommittedDiff): Avoid false-positive diffs in getUncommittedDiff by detecting symlinked directories and skipping identical files 2025-11-04 03:40:49 +00:00
jkunz 238dd152ba 3.3.0 2025-11-04 01:26:54 +00:00
jkunz 9bacb2e548 feat(GitRepo): Add glob-pattern exclusions for getUncommittedDiff and add minimatch; bump dependencies 2025-11-04 01:26:54 +00:00
jkunz 5ba6c5370e 3.2.1 2025-08-04 15:16:42 +00:00
jkunz c6e3a1caa3 feat(core): enhance error handling, type safety, and documentation
- Add comprehensive error handling with try/catch blocks and meaningful error messages
- Improve type safety with proper IEnvDeps interface replacing 'any' types
- Add complete JSDoc documentation for all classes and methods
- Add return type annotations for better TypeScript support
- Add ensureInitialized() validation method
- Fix missing return statement in createRepoByClone() method
- Remove deprecated @types/minimatch dependency
- Complete readme rewrite with modern styling, accurate documentation, and proper API examples
- Update license from LICENSE to license.md following project guidelines
2025-08-04 15:12:23 +00:00
jkunz a92275088b update 2025-08-04 14:32:04 +00:00
jkunz 2b246502f5 update deps 2025-08-04 14:01:52 +00:00
philkunz 81b2b225a3 fix(changelog): Update to neweset format. 2024-06-23 23:42:47 +02:00
philkunz 9a2d5afc04 3.1.1 2024-06-23 23:37:30 +02:00
philkunz 6404c8342a fix(documentation): Remove outdated changelog entries 2024-06-23 23:37:29 +02:00
philkunz b14ae1d8a6 3.1.0 2024-06-23 21:05:51 +02:00
philkunz 753bc49c4e feat(gitrepo): Enhance GitRepo to include commit date and version information 2024-06-23 21:05:50 +02:00
philkunz a5dae9b3a3 3.0.4 2024-06-22 10:01:16 +02:00
philkunz af909047ae fix(core): update 2024-06-22 10:01:15 +02:00
philkunz 11cbe93b4b 3.0.3 2024-06-22 02:44:20 +02:00
philkunz e8c3d3c44f fix(GitRepo): add diff function 2024-06-22 02:44:19 +02:00
philkunz a2485560b3 update description 2024-05-29 14:13:21 +02:00
philkunz 7aa83ac730 update tsconfig 2024-04-14 17:38:19 +02:00
philkunz 141af9843f update npmextra.json: githost 2024-04-01 21:35:12 +02:00
philkunz 71e84e79b1 update npmextra.json: githost 2024-04-01 19:58:19 +02:00
philkunz aefcf56f0e update npmextra.json: githost 2024-03-30 21:47:17 +01:00
philkunz ace668bfc1 3.0.2 2023-11-15 15:15:18 +01:00
philkunz f2949c5f21 fix(core): update 2023-11-15 15:15:18 +01:00
philkunz 6ebb7cec81 3.0.1 2023-07-27 13:45:49 +02:00
philkunz c64de19815 fix(core): update 2023-07-27 13:45:48 +02:00
philkunz 26d4753ddf switch to new org scheme 2023-07-11 00:44:24 +02:00
philkunz 6d5b9f3553 switch to new org scheme 2023-07-10 02:56:04 +02:00
philkunz 8dd8bd0be1 3.0.0 2022-07-31 15:37:57 +02:00
philkunz 0bd91c2bda BREAKING CHANGE(core): switch to esm 2022-07-31 15:37:57 +02:00
philkunz 6ef3e6e6f5 2.0.5 2022-07-31 15:19:20 +02:00
philkunz 93709f920e fix(core): update 2022-07-31 15:19:19 +02:00
philkunz 75fa530f73 2.0.4 2022-07-31 15:18:04 +02:00
philkunz f5617d9d45 fix(core): update 2022-07-31 15:18:04 +02:00
philkunz b46d1e19a7 2.0.3 2022-07-31 15:16:06 +02:00
philkunz c8d173807d fix(core): update 2022-07-31 15:16:06 +02:00
philkunz 5944c7e96f 2.0.2 2022-07-31 15:14:19 +02:00
philkunz 39ea160fdf fix(core): update 2022-07-31 15:14:18 +02:00
philkunz 91ca5e53f1 2.0.1 2021-10-22 01:42:42 +02:00
philkunz 778267bb36 fix(core): update 2021-10-22 01:42:42 +02:00
23 changed files with 9771 additions and 27275 deletions
+66
View File
@@ -0,0 +1,66 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
+124
View File
@@ -0,0 +1,124 @@
name: Default (tags)
on:
push:
tags:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @git.zone/tsdoc
npmci command tsdoc
continue-on-error: true
-137
View File
@@ -1,137 +0,0 @@
# gitzone ci_default
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: '$CI_BUILD_STAGE'
stages:
- security
- test
- release
- metadata
# ====================
# security stage
# ====================
mirror:
stage: security
script:
- npmci git mirror
only:
- tags
tags:
- lossless
- docker
- notpriv
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --production --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=prod --production
tags:
- docker
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=dev
tags:
- docker
allow_failure: true
# ====================
# test stage
# ====================
testStable:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
testBuild:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
release:
stage: release
script:
- npmci node install stable
- npmci npm publish
only:
- tags
tags:
- lossless
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
allow_failure: true
only:
- tags
script:
- npmci command npm install -g tslint typescript
- npmci npm prepare
- npmci npm install
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
tags:
- lossless
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- lossless
- docker
- notpriv
pages:
stage: metadata
script:
- npmci node install lts
- npmci command npm install -g @gitzone/tsdoc
- npmci npm prepare
- npmci npm install
- npmci command tsdoc
tags:
- lossless
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
allow_failure: true
+28
View File
@@ -0,0 +1,28 @@
{
"@git.zone/cli": {
"projectType": "npm",
"module": {
"githost": "code.foss.global",
"gitscope": "push.rocks",
"gitrepo": "smartgit",
"description": "A smart wrapper for nodegit that simplifies Git operations in Node.js.",
"npmPackagename": "@push.rocks/smartgit",
"license": "MIT",
"projectDomain": "push.rocks"
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}
},
"@git.zone/tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@ship.zone/szci": {
"npmGlobalTools": [],
"npmRegistryUrl": "registry.npmjs.org"
}
}
+3 -21
View File
@@ -2,28 +2,10 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "current file", "command": "npm test",
"type": "node", "name": "Run npm test",
"request": "launch", "request": "launch",
"args": [ "type": "node-terminal"
"${relativeFile}"
],
"runtimeArgs": ["-r", "@gitzone/tsrun"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "test.ts",
"type": "node",
"request": "launch",
"args": [
"test/test.ts"
],
"runtimeArgs": ["-r", "@gitzone/tsrun"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
} }
] ]
} }
+102
View File
@@ -0,0 +1,102 @@
# Changelog
## 2026-04-30 - 3.3.2 - fix(build)
modernize project configuration and tighten Node.js typing support
- migrate npmextra configuration to namespaced tool settings and add .smartconfig.json
- update build and test scripts alongside TypeScript and dependency versions
- refactor Smartgit to expose validated fs/http accessors and improve return type annotations
- adjust tests and repository URLs to use code.foss.global
## 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)
Enhanced error handling, type safety, and documentation
- Add comprehensive error handling with try/catch blocks and meaningful error messages
- Improve type safety with proper IEnvDeps interface replacing 'any' types
- Add complete JSDoc documentation for all classes and methods
- Add return type annotations for better TypeScript support
- Add ensureInitialized() validation method
- Fix missing return statement in createRepoByClone() method
- Remove deprecated @types/minimatch dependency
- Complete readme rewrite with modern styling, accurate documentation, and proper API examples
- Update license from LICENSE to license.md following project guidelines
## 2024-06-23 - 3.1.1 - fix(documentation)
Remove outdated changelog entries
## 2024-06-23 - 3.1.0 - gitrepo
Enhancements and fixes to GitRepo
- Enhance GitRepo to include commit date and version information
## 2024-06-22 - 3.0.3 to 3.0.4 - core & GitRepo
General updates and new feature addition
- Fixed core functionality
- Added diff function in GitRepo
## 2023-11-15 - 3.0.1 to 3.0.2 - core, tsconfig, npmextra
Minor updates and fixes
- Fixed core functionality
- Updated tsconfig and npmextra.json
## 2023-07-10 - 3.0.0 - core
Structural changes to organization scheme
- Switched to a new organizational scheme
## 2023-07-27 - 3.0.0 to 3.0.1 - core
Structural changes and updates
- Fixed core functionality
- Switched to new organizational scheme
## 2022-07-31 - 2.0.2 to 3.0.0 - core
Breaking changes and updates
- Switching to ESM for core
- Fixed core functionality
## 2021-10-22 - 1.0.18 to 2.0.1 - dependencies, core
Breaking changes and updates
- Switched to isomorphic git dependencies
- Fixed core functionality
## 2020-08-15 - 1.0.14 to 1.0.18 - core
Fixes
- Fixed core functionality in multiple patches
## 2019-06-18 - 1.0.5 to 1.0.10 - core
Fixes
- Fixed core functionality in multiple patches
## 2016-06-23 - 0.0.10 to 0.1.11 - gitlab & other fixes
Initial implementations and setup
- Fixed README and merge issues
- Updated gitlab.yml and CI settings
- Implemented new class approach and other updates
- Removed unnecessary imports, postinstall
- Added npmextra.json, CI tests, SSH key support
+1 -2
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2015 Lossless GmbH Copyright (c) 2026 Task Venture Capital GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2026 Task Venture Capital GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+33 -11
View File
@@ -1,17 +1,39 @@
{ {
"npmci": { "@git.zone/cli": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"gitzone": {
"projectType": "npm", "projectType": "npm",
"module": { "module": {
"githost": "gitlab.com", "githost": "code.foss.global",
"gitscope": "pushrocks", "gitscope": "push.rocks",
"gitrepo": "smartgit", "gitrepo": "smartgit",
"shortDescription": "smart wrapper for nodegit", "description": "A smart wrapper for nodegit that simplifies Git operations in Node.js.",
"npmPackagename": "@pushrocks/smartgit", "npmPackagename": "@push.rocks/smartgit",
"license": "MIT" "license": "MIT",
"projectDomain": "push.rocks",
"keywords": [
"git",
"nodegit",
"version control",
"git wrapper",
"programming",
"development",
"code management",
"repository management",
"git operations"
]
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
} }
},
"@git.zone/tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@ship.zone/szci": {
"npmGlobalTools": [],
"npmRegistryUrl": "registry.npmjs.org"
} }
} }
-26916
View File
File diff suppressed because it is too large Load Diff
+37 -25
View File
@@ -1,44 +1,53 @@
{ {
"name": "@pushrocks/smartgit", "name": "@push.rocks/smartgit",
"version": "2.0.0", "version": "3.3.2",
"description": "smart wrapper for nodegit", "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",
"type": "module",
"scripts": { "scripts": {
"test": "(tstest test/)", "test": "tstest test/ --verbose",
"build": "(tsbuild --web)" "build": "tsbuild --web",
"buildDocs": "tsdoc"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitlab.com/pushrocks/smartgit.git" "url": "https://code.foss.global/push.rocks/smartgit.git"
}, },
"keywords": [ "keywords": [
"json", "git",
"jade", "nodegit",
"template" "version control",
"git wrapper",
"programming",
"development",
"code management",
"repository management",
"git operations"
], ],
"author": "Smart Coordination GmbH <office@push.rocks> (https://push.rocks)", "author": "Smart Coordination GmbH <office@push.rocks> (https://push.rocks)",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://gitlab.com/pushrocks/smartgit/issues" "url": "https://code.foss.global/push.rocks/smartgit/issues"
}, },
"homepage": "https://gitlab.com/pushrocks/smartgit", "homepage": "https://code.foss.global/push.rocks/smartgit",
"dependencies": { "dependencies": {
"@pushrocks/smartenv": "^4.0.16", "@push.rocks/smartenv": "^6.0.0",
"@pushrocks/smartfile": "^8.0.10", "@push.rocks/smartfile": "^13.1.3",
"@pushrocks/smartpath": "^4.0.3", "@push.rocks/smartpath": "^6.0.0",
"@pushrocks/smartpromise": "^3.1.6", "@push.rocks/smartpromise": "^4.2.3",
"@pushrocks/smartshell": "^2.0.28", "@push.rocks/smartshell": "^3.3.8",
"@pushrocks/smartstring": "^3.0.24", "@push.rocks/smartstring": "^4.1.0",
"@types/minimatch": "^3.0.5", "@push.rocks/smarttime": "^4.2.3",
"isomorphic-git": "^1.10.1" "diff": "^9.0.0",
"isomorphic-git": "^1.37.6",
"minimatch": "^10.2.5"
}, },
"devDependencies": { "devDependencies": {
"@gitzone/tsbuild": "^2.1.28", "@git.zone/tsbuild": "^4.4.0",
"@gitzone/tstest": "^1.0.59", "@git.zone/tsrun": "^2.0.3",
"@pushrocks/tapbundle": "^3.2.14", "@git.zone/tstest": "^3.6.3",
"tslint": "^6.1.3", "@types/node": "^25.6.0"
"tslint-config-prettier": "^1.18.0"
}, },
"private": false, "private": false,
"files": [ "files": [
@@ -50,10 +59,13 @@
"dist_ts_web/**/*", "dist_ts_web/**/*",
"assets/**/*", "assets/**/*",
"cli.js", "cli.js",
".smartconfig.json",
"license",
"npmextra.json", "npmextra.json",
"readme.md" "readme.md"
], ],
"browserslist": [ "browserslist": [
"last 1 chrome versions" "last 1 chrome versions"
] ],
"packageManager": "pnpm@10.28.2"
} }
+8754
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- mongodb-memory-server
- puppeteer
+32
View File
@@ -0,0 +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()`
+219 -69
View File
@@ -1,88 +1,238 @@
# @pushrocks/smartgit # @push.rocks/smartgit
smart wrapper for nodegit
## Availabililty and Links > 🚀 **Modern Git operations for Node.js** - A powerful TypeScript wrapper around isomorphic-git that makes repository management and analysis a breeze
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/smartgit)
* [gitlab.com (source)](https://gitlab.com/pushrocks/smartgit)
* [github.com (source mirror)](https://github.com/pushrocks/smartgit)
* [docs (typedoc)](https://pushrocks.gitlab.io/smartgit/)
## Status for master [![npm version](https://badge.fury.io/js/@push.rocks%2Fsmartgit.svg)](https://badge.fury.io/js/@push.rocks%2Fsmartgit)
[![TypeScript](https://badgen.net/badge/built%20with/TypeScript/blue)](https://www.typescriptlang.org/)
Status Category | Status Badge ## ✨ What is SmartGit?
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/pushrocks/smartgit/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/pushrocks/smartgit/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@pushrocks/smartgit)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/pushrocks/smartgit)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartgit)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartgit)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartgit)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage SmartGit is a sophisticated, promise-based Git toolkit designed for Node.js applications. Built on top of `isomorphic-git`, it provides a clean, intuitive API for repository management, diff analysis, and commit history exploration. Perfect for automation tools, CI/CD pipelines, and any application that needs to interact with Git repositories programmatically.
We recommend the use of TypeScript for best in class intellisense ## 🎯 Key Features
```javascript - **🔧 Repository Management** - Create, clone, and open repositories with ease
// import smartgit: - **🌐 Remote Operations** - Manage remotes and push changes effortlessly
import { GitRepo } from 'smartgit'; - **📊 Diff Analysis** - Get detailed diffs of uncommitted changes
- **📚 Commit History** - Extract commit messages with metadata and version tracking
- **⚡ Async/Await** - Modern promise-based API throughout
- **🛡️ TypeScript First** - Full type safety and IntelliSense support
- **🎯 Production Ready** - Battle-tested and actively maintained
// Initialize smartgit: ## 📦 Installation
// -- note: there are 3 ways to initialize smartgit
// -- -- 1. with a existing Git repo
// -- -- 2. with a cloned Git repo
// -- -- 3. with a new Git repo
// -- 1. existing Git Repo: ```bash
let myExistingGitRepo = new GitRepo('/path/to/existing/git/repo/'); # Using pnpm (recommended)
pnpm install @push.rocks/smartgit
// -- 2. cloned Git Repo: # Using npm
let myClonedGitRepo: GitRepo; npm install @push.rocks/smartgit
smartgit.createRepoFromClone('git@github.com:username/reponame.git').then((gitRepo) => {
// non blocking
myClonedGitRepo = gitRepo;
});
// -- 3. new Git Repo # Using yarn
let myNewGitRepo: GitRepo; yarn add @push.rocks/smartgit
smartgit
.createRepoFromInit('/path/to/new/folder') // smartgit will create any new folder, be careful
.then((gitRepo) => {
// non blocking
myNewGitRepo = gitRepo;
});
// Using smartgit instance
// -- most used actions
// -- all actions return promises, so make sure to use promise chaining for any dependent tasks
myExistingGitRepo.addAll(); // returns promise, stages all changed files
myExistingGitRepo.add(['relative/path/to/file.txt', 'another/file2.txt']); // returns promise, stages specific files
myExistingGitRepo.commit('my commit message'); // returns promise, commits staged files
myExistingGitRepo
.status() // returns promise
.then((status) => {
// Use TypeScript for status type information
});
myExistingGitRepo.check(); // returns promise, checks repo health
myExistingGitRepo.remoteAdd('git@github.com:username/reponame.git');
``` ```
Tip: use [smartssh](https://npmjs.com/smartssh) to setup your SSH environment ## 🚀 Quick Start
[![npm](https://push.rocks/assets/repo-header.svg)](https://push.rocks) ```typescript
import { Smartgit } from '@push.rocks/smartgit';
// Initialize SmartGit
const smartgit = new Smartgit();
await smartgit.init();
## Contribution // Clone a repository
const repo = await smartgit.createRepoByClone(
'https://github.com/username/repo.git',
'./local-repo'
);
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :) console.log('🎉 Repository cloned successfully!');
```
For further information read the linked docs at the top of this readme. ## 📖 Usage Guide
> MIT licensed | **&copy;** [Lossless GmbH](https://lossless.gmbh) ### 🏗️ Repository Operations
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com) #### Creating a New Repository
```typescript
const repo = await smartgit.createRepoByInit('./my-new-repo');
```
#### Cloning from Remote
```typescript
const repo = await smartgit.createRepoByClone(
'https://github.com/octocat/Hello-World.git',
'./hello-world'
);
```
#### Opening Existing Repository
```typescript
const repo = await smartgit.createRepoByOpen('./existing-repo');
```
### 🌐 Working with Remotes
#### List All Remotes
```typescript
const remotes = await repo.listRemotes();
console.log(remotes);
// Output: [{ remote: 'origin', url: 'https://github.com/...' }]
```
#### Ensure Remote Exists
```typescript
await repo.ensureRemote('origin', 'https://github.com/username/repo.git');
```
#### Get Remote URL
```typescript
const originUrl = await repo.getUrlForRemote('origin');
console.log(`📡 Origin URL: ${originUrl}`);
```
#### Push to Remote
```typescript
await repo.pushBranchToRemote('main', 'origin');
console.log('✅ Changes pushed successfully!');
```
### 📊 Diff Analysis
Get a detailed diff of uncommitted changes (perfect for code review automation):
```typescript
const diffs = await repo.getUncommittedDiff(['node_modules/*', '*.log']);
diffs.forEach(diff => {
console.log('📝 File changes:');
console.log(diff);
});
```
### 📚 Commit History Analysis
Extract commit history with package.json version tracking:
```typescript
const history = await repo.getAllCommitMessages();
history.forEach(commit => {
console.log(`🕐 ${commit.date} | v${commit.version} | ${commit.message}`);
});
// Example output:
// 🕐 2024-01-15 | v1.2.3 | feat: add new authentication method
// 🕐 2024-01-14 | v1.2.2 | fix: resolve memory leak in parser
```
## 🛠️ Advanced Usage
### Error Handling
```typescript
try {
const repo = await smartgit.createRepoByClone(invalidUrl, './test');
} catch (error) {
console.error('❌ Clone failed:', error.message);
}
```
### Working with Multiple Repositories
```typescript
const smartgit = new Smartgit();
await smartgit.init();
const repositories = [
await smartgit.createRepoByOpen('./repo1'),
await smartgit.createRepoByOpen('./repo2'),
await smartgit.createRepoByOpen('./repo3')
];
// Analyze all repositories
for (const repo of repositories) {
const remotes = await repo.listRemotes();
console.log(`📂 Repository has ${remotes.length} remotes`);
}
```
## 🏗️ Architecture
SmartGit is built with a clean, modular architecture:
- **`Smartgit`** - Main entry point and repository factory
- **`GitRepo`** - Individual repository operations and analysis
- **`isomorphic-git`** - Underlying Git implementation
- **Environment Detection** - Automatic Node.js environment setup
## 🎯 Use Cases
- **🤖 Automation Scripts** - Automate repository management and analysis
- **🔄 CI/CD Pipelines** - Repository operations in build processes
- **📊 Code Analysis Tools** - Extract commit data and diff analysis
- **🛠️ Developer Tools** - Build Git-powered development utilities
- **📱 Release Management** - Track versions and generate changelogs
## 🔧 Requirements
- **Node.js** 16+ (Browser support not available)
- **Git repository** access (for clone operations)
- **TypeScript** 4+ (recommended)
## 🤝 API Reference
### Smartgit Class
| Method | Description | Returns |
|--------|-------------|---------|
| `init()` | Initialize the SmartGit instance | `Promise<void>` |
| `createRepoByInit(dir)` | Create new repository | `Promise<GitRepo>` |
| `createRepoByClone(url, dir)` | Clone repository | `Promise<GitRepo>` |
| `createRepoByOpen(dir)` | Open existing repository | `Promise<GitRepo>` |
### GitRepo Class
| Method | Description | Returns |
|--------|-------------|---------|
| `listRemotes()` | List all remotes | `Promise<{remote: string, url: string}[]>` |
| `ensureRemote(name, url)` | Ensure remote exists | `Promise<void>` |
| `getUrlForRemote(name)` | Get URL for remote | `Promise<string>` |
| `pushBranchToRemote(branch, remote)` | Push branch to remote | `Promise<void>` |
| `getUncommittedDiff(excludeFiles?)` | Get uncommitted changes | `Promise<string[]>` |
| `getAllCommitMessages()` | Get commit history | `Promise<CommitInfo[]>` |
## 💡 Pro Tips
- **🔧 Always call `init()`** before using any repository operations
- **📁 Use absolute paths** for repository directories when possible
- **🚫 Exclude large files** from diff analysis using the `excludeFiles` parameter
- **⚡ Batch operations** when working with multiple repositories
- **🛡️ Handle errors gracefully** - network issues can cause clone operations to fail
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
+38 -9
View File
@@ -1,24 +1,53 @@
import { tap, expect } from '@pushrocks/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as smartgit from '../ts/index'; import * as smartgit from '../ts/index.js';
import * as path from 'path'; import * as smartpath from '@push.rocks/smartpath';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
let testSmartgitInstance: smartgit.Smartgit; let testSmartgitInstance: smartgit.Smartgit;
const testRepoDir = path.join(__dirname, '../.nogit/testrepo'); const packageDir = path.join(smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
const testRepoDirSmartfile = path.join(__dirname, '../.nogit/pushrocks_smartfile') const testRepoDir = path.join(packageDir, './.nogit/testrepo');
const testRepoDirSmartfile = path.join(packageDir, './.nogit/pushrocks_smartfile');
tap.test('should create a valid smartgit instance', async () => { tap.test('should create a valid smartgit instance', async () => {
testSmartgitInstance = new smartgit.Smartgit(); testSmartgitInstance = new smartgit.Smartgit();
await testSmartgitInstance.init(); await testSmartgitInstance.init();
expect(testSmartgitInstance).to.be.instanceOf(smartgit.Smartgit); expect(testSmartgitInstance).toBeInstanceOf(smartgit.Smartgit);
}) });
tap.test('should create a new repo at .nogit', async () => { tap.test('should create a new repo at .nogit', async () => {
const gitRepo = await testSmartgitInstance.createRepoByOpen(testRepoDir); const gitRepo = await testSmartgitInstance.createRepoByOpen(testRepoDir);
}); });
tap.test('should clone a repo', async () => { tap.test('should clone a repo', async () => {
const gitRepo = await testSmartgitInstance.createRepoByClone('https://gitlab.com/pushrocks/smartfile.git', testRepoDirSmartfile); await fs.rm(testRepoDirSmartfile, { recursive: true, force: true });
const gitRepo = await testSmartgitInstance.createRepoByClone(
'https://code.foss.global/push.rocks/smartfile.git',
testRepoDirSmartfile
);
}); });
tap.start(); tap.test('should open a repo', async () => {
const gitRepo = await testSmartgitInstance.createRepoByOpen(packageDir);
const diff = await gitRepo.getUncommittedDiff([
'pnpm-lock.yaml',
]);
console.log(diff);
});
tap.test('should get the diff to HEAD', async () => {
const gitRepo = await testSmartgitInstance.createRepoByOpen(packageDir);
const diff = await gitRepo.getUncommittedDiff([
'pnpm-lock.yaml',
]);
console.log(diff);
});
tap.test('should print all commit messages', async () => {
const gitRepo = await testSmartgitInstance.createRepoByOpen(packageDir);
const commitMessages = await gitRepo.getAllCommitMessages();
console.log(commitMessages);
});
export default tap.start();
+8
View File
@@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@push.rocks/smartgit',
version: '3.3.2',
description: 'A smart wrapper for nodegit that simplifies Git operations in Node.js.'
}
+2 -4
View File
@@ -1,4 +1,2 @@
import plugins = require('./smartgit.plugins'); export * from './smartgit.classes.gitrepo.js';
export * from './smartgit.classes.smartgit.js';
export * from './smartgit.classes.gitrepo';
export * from './smartgit.classes.smartgit';
+171 -25
View File
@@ -1,6 +1,5 @@
import * as plugins from './smartgit.plugins'; import * as plugins from './smartgit.plugins.js';
import { Smartgit } from './smartgit.classes.smartgit.js';
import { Smartgit } from './smartgit.classes.smartgit';
/** /**
* class GitRepo allows access to git directories from node * class GitRepo allows access to git directories from node
@@ -18,8 +17,8 @@ export class GitRepo {
const dirArg = plugins.path.resolve(toArg); const dirArg = plugins.path.resolve(toArg);
await plugins.isomorphicGit.clone({ await plugins.isomorphicGit.clone({
dir: toArg, dir: toArg,
fs: smartgitRefArg.envDeps.fs, fs: smartgitRefArg.fs,
http: smartgitRefArg.envDeps.http, http: smartgitRefArg.http,
url: fromArg, url: fromArg,
}); });
return new GitRepo(smartgitRefArg, toArg); return new GitRepo(smartgitRefArg, toArg);
@@ -32,12 +31,12 @@ export class GitRepo {
dirArg = plugins.path.resolve(dirArg); dirArg = plugins.path.resolve(dirArg);
await plugins.isomorphicGit.init({ await plugins.isomorphicGit.init({
dir: dirArg, dir: dirArg,
fs: smartgitRefArg.envDeps.fs, fs: smartgitRefArg.fs,
}); });
return new GitRepo(smartgitRefArg, dirArg); return new GitRepo(smartgitRefArg, dirArg);
} }
public static async fromOpeningRepoDir(smartgitRefArg: Smartgit, dirArg: string) { public static async fromOpeningRepoDir(smartgitRefArg: Smartgit, dirArg: string): Promise<GitRepo> {
dirArg = plugins.path.resolve(dirArg); dirArg = plugins.path.resolve(dirArg);
return new GitRepo(smartgitRefArg, dirArg); return new GitRepo(smartgitRefArg, dirArg);
} }
@@ -61,54 +60,201 @@ export class GitRepo {
}[] }[]
> { > {
const remotes = await plugins.isomorphicGit.listRemotes({ const remotes = await plugins.isomorphicGit.listRemotes({
fs: this.smartgitRef.envDeps.fs, fs: this.smartgitRef.fs,
dir: this.repoDir, dir: this.repoDir,
}); });
return remotes; return remotes;
} }
/** /**
* ensures the existance of a remote within a repository * ensures the existence of a remote within a repository
* @param remoteNameArg * @param remoteNameArg
* @param remoteUrlArg * @param remoteUrlArg
*/ */
public async ensureRemote(remoteNameArg: string, remoteUrlArg: string): Promise<void> { public async ensureRemote(remoteNameArg: string, remoteUrlArg: string): Promise<void> {
const remotes = await this.listRemotes(); const remotes = await this.listRemotes();
const existingRemote = const existingRemote = remotes.find((itemArg) => itemArg.remote === remoteNameArg);
remotes.find((itemArg) => itemArg.remote === remoteNameArg);
if (existingRemote) { if (existingRemote) {
if (existingRemote.url !== remoteUrlArg) { if (existingRemote.url !== remoteUrlArg) {
await plugins.isomorphicGit.deleteRemote({ await plugins.isomorphicGit.deleteRemote({
remote: remoteNameArg, remote: remoteNameArg,
fs: this.smartgitRef.envDeps.fs, fs: this.smartgitRef.fs,
dir: this.repoDir dir: this.repoDir,
}) });
} else { } else {
return; return;
}; }
} }
await plugins.isomorphicGit.addRemote({ await plugins.isomorphicGit.addRemote({
remote: remoteNameArg, remote: remoteNameArg,
fs: this.smartgitRef.envDeps.fs, fs: this.smartgitRef.fs,
url: remoteUrlArg url: remoteUrlArg,
}) });
} }
/** /**
* gets the url for a specific remote * gets the url for a specific remote
*/ */
public async getUrlForRemote(remoteName: string): Promise<string> { public async getUrlForRemote(remoteName: string): Promise<string | undefined> {
const remotes = await this.listRemotes(); const remotes = await this.listRemotes();
const existingRemote = remotes.find(remoteArg => remoteArg.remote === remoteName); const existingRemote = remotes.find((remoteArg) => remoteArg.remote === remoteName);
return existingRemote?.url; return existingRemote?.url;
} }
public async pushBranchToRemote(branchName: string, remoteName: string) { public async pushBranchToRemote(branchName: string, remoteName: string): Promise<void> {
await plugins.isomorphicGit.push({ await plugins.isomorphicGit.push({
fs: this.smartgitRef.envDeps.fs, fs: this.smartgitRef.fs,
http: this.smartgitRef.envDeps.http, http: this.smartgitRef.http,
ref: branchName, ref: branchName,
remote: remoteName remote: remoteName,
}) });
}
/**
* Get the diff of the current uncommitted changes while excluding specified files
*/
public async getUncommittedDiff(excludeFiles: string[] = []): Promise<string[]> {
const statusMatrix = await plugins.isomorphicGit.statusMatrix({
fs: this.smartgitRef.fs,
dir: this.repoDir,
});
const diffs: string[] = [];
for (const row of statusMatrix) {
const [filepath, head, workdir] = row;
if (excludeFiles.some(pattern => filepath === pattern || plugins.minimatch(filepath, pattern))) {
continue; // Skip excluded files (supports exact matches and glob patterns)
}
let headContent = '';
let workdirContent = '';
// Handle modified files
if (head !== 0 && workdir !== 0 && head !== workdir) {
headContent = await plugins.isomorphicGit
.readBlob({
fs: this.smartgitRef.fs,
dir: this.repoDir,
oid: await plugins.isomorphicGit.resolveRef({
fs: this.smartgitRef.fs,
dir: this.repoDir,
ref: 'HEAD',
}),
filepath,
})
.then((result) => new TextDecoder().decode(result.blob));
workdirContent = await this.smartgitRef.fs.promises.readFile(
plugins.path.join(this.repoDir, filepath),
'utf8'
);
}
// Handle added files
if (head === 0 && workdir !== 0) {
workdirContent = await this.smartgitRef.fs.promises.readFile(
plugins.path.join(this.repoDir, filepath),
'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.fs,
dir: this.repoDir,
oid: await plugins.isomorphicGit.resolveRef({
fs: this.smartgitRef.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 instanceof Error && 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
if (head !== 0 && workdir === 0) {
headContent = await plugins.isomorphicGit
.readBlob({
fs: this.smartgitRef.fs,
dir: this.repoDir,
oid: await plugins.isomorphicGit.resolveRef({
fs: this.smartgitRef.fs,
dir: this.repoDir,
ref: 'HEAD',
}),
filepath,
})
.then((result) => new TextDecoder().decode(result.blob));
}
if (headContent || workdirContent) {
// Skip files with identical content (filters false positives from statusMatrix)
if (headContent === workdirContent) {
continue;
}
const diff = plugins.diff.createTwoFilesPatch(
filepath,
filepath,
headContent,
workdirContent
);
diffs.push(diff);
}
}
return diffs;
}
/**
* Get all commit messages with their dates and package.json version at each commit
*/
public async getAllCommitMessages(): Promise<
{ date: string; version: string; message: string }[]
> {
const commits = await plugins.isomorphicGit.log({
fs: this.smartgitRef.fs,
dir: this.repoDir,
});
const results: { date: string; version: string; message: string }[] = [];
for (const commit of commits) {
let version = 'unknown';
try {
const packageJsonBlob = await plugins.isomorphicGit.readBlob({
fs: this.smartgitRef.fs,
dir: this.repoDir,
oid: commit.oid,
filepath: 'package.json',
});
const packageJson = JSON.parse(new TextDecoder().decode(packageJsonBlob.blob)) as {
version?: string;
};
version = packageJson.version ?? 'unknown';
} catch (error) {
// If package.json does not exist or any error occurs, leave version as 'unknown'
}
results.push({
date: new plugins.smarttime.ExtendedDate(commit.commit.committer.timestamp * 1000).exportToHyphedSortableDate(),
version: version,
message: commit.commit.message,
});
}
return results;
} }
} }
+102 -30
View File
@@ -1,36 +1,108 @@
import * as plugins from './smartgit.plugins'; import * as plugins from './smartgit.plugins.js';
import { GitRepo } from './smartgit.classes.gitrepo'; import { GitRepo } from './smartgit.classes.gitrepo.js';
type TNodeFs = typeof import('node:fs');
interface IEnvDeps {
fs?: TNodeFs;
http?: plugins.HttpClient;
}
/**
* class Smartgit provides a high-level interface for git operations
* Must be initialized before use by calling init()
*/
export class Smartgit { export class Smartgit {
public smartenvInstance = new plugins.smartenv.Smartenv(); public smartenvInstance = new plugins.smartenv.Smartenv();
public envDeps: { public envDeps: IEnvDeps = {
fs: any; fs: undefined,
http: any; http: undefined,
} = { };
fs: null,
http: null
}
constructor() {}; /**
* initializes the Smartgit instance with required environment dependencies
public async init() { * Must be called before using any repository methods
if (this.smartenvInstance.isNode) { */
this.envDeps.fs = this.smartenvInstance.getSafeNodeModule('fs'); public async init(): Promise<void> {
this.envDeps.http = this.smartenvInstance.getSafeNodeModule('isomorphic-git/http/node'); try {
if (this.smartenvInstance.isNode) {
this.envDeps.fs = await this.smartenvInstance.getSafeNodeModule<TNodeFs>('fs');
this.envDeps.http = await this.smartenvInstance.getSafeNodeModule<plugins.HttpClient>(
'isomorphic-git/http/node'
);
} else {
throw new Error('currently only node.js is supported.');
}
} catch (error) {
throw new Error(`Failed to initialize Smartgit: ${error instanceof Error ? error.message : String(error)}`);
} }
};
public async createRepoByClone(fromUrlArg: string, toDirArg: string) {
const repo = await GitRepo.fromCloningIntoDir(this, fromUrlArg, toDirArg)
};
public async createRepoByInit(dirArg: string) {
const repo = await GitRepo.fromCreatingRepoInDir(this, dirArg);
return repo;
};
public async createRepoByOpen(dirArg: string) {
const repo = await GitRepo.fromOpeningRepoDir(this, dirArg);
return repo;
} }
}
private ensureInitialized(): void {
if (!this.envDeps.fs || !this.envDeps.http) {
throw new Error('Smartgit must be initialized before use. Call init() first.');
}
}
public get fs(): TNodeFs {
const fs = this.envDeps.fs;
if (!fs) {
throw new Error('Smartgit must be initialized before use. Call init() first.');
}
return fs;
}
public get http(): plugins.HttpClient {
const http = this.envDeps.http;
if (!http) {
throw new Error('Smartgit must be initialized before use. Call init() first.');
}
return http;
}
/**
* creates a new GitRepo instance by cloning from a remote URL
* @param fromUrlArg the URL to clone from
* @param toDirArg the directory to clone into
* @returns Promise<GitRepo> the created repository instance
*/
public async createRepoByClone(fromUrlArg: string, toDirArg: string): Promise<GitRepo> {
this.ensureInitialized();
try {
const repo = await GitRepo.fromCloningIntoDir(this, fromUrlArg, toDirArg);
return repo;
} catch (error) {
throw new Error(`Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* creates a new GitRepo instance by initializing a new repository in a directory
* @param dirArg the directory to initialize the repository in
* @returns Promise<GitRepo> the created repository instance
*/
public async createRepoByInit(dirArg: string): Promise<GitRepo> {
this.ensureInitialized();
try {
const repo = await GitRepo.fromCreatingRepoInDir(this, dirArg);
return repo;
} catch (error) {
throw new Error(`Failed to initialize repository: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* creates a new GitRepo instance by opening an existing repository in a directory
* @param dirArg the directory containing the existing repository
* @returns Promise<GitRepo> the opened repository instance
*/
public async createRepoByOpen(dirArg: string): Promise<GitRepo> {
this.ensureInitialized();
try {
const repo = await GitRepo.fromOpeningRepoDir(this, dirArg);
return repo;
} catch (error) {
throw new Error(`Failed to open repository: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
+12 -9
View File
@@ -1,17 +1,20 @@
// node native // node native
import * as path from 'path'; import * as path from 'node:path';
export { path }; export { path };
import * as smartenv from '@pushrocks/smartenv'; import * as smartenv from '@push.rocks/smartenv';
import * as smartfile from '@pushrocks/smartfile'; import * as smartfile from '@push.rocks/smartfile';
import * as smartpath from '@pushrocks/smartpath'; import * as smartpath from '@push.rocks/smartpath';
import * as smartpromise from '@pushrocks/smartpromise'; import * as smartpromise from '@push.rocks/smartpromise';
import * as smartstring from '@pushrocks/smartstring'; import * as smartstring from '@push.rocks/smartstring';
import * as smarttime from '@push.rocks/smarttime';
export { smartenv, smartfile, smartpath, smartpromise, smartstring }; export { smartenv, smartfile, smartpath, smartpromise, smartstring, smarttime };
// third party // third party
import isomorphicGit from 'isomorphic-git'; import * as diff from 'diff';
import isomorphicGit, { type HttpClient } from 'isomorphic-git';
import { minimatch } from 'minimatch';
export { isomorphicGit }; export { diff, isomorphicGit, minimatch, type HttpClient };
+14
View File
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noImplicitAny": true,
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"types": ["node"]
},
"exclude": ["dist_*/**/*.d.ts"]
}
-17
View File
@@ -1,17 +0,0 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"semicolon": [true, "always"],
"no-console": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"member-ordering": {
"options":{
"order": [
"static-method"
]
}
}
},
"defaultSeverity": "warning"
}