Compare commits
No commits in common. "6ca2c4d25ffb66b3fda37024a3c7097d0e0b8ba2" and "bc9b0ce88f11d6db0acd3dcf937a38197b148d62" have entirely different histories.
6ca2c4d25f
...
bc9b0ce88f
|
|
@ -4,12 +4,9 @@ name: go-lib/util
|
|||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.25.8
|
||||
image: golang:1.18
|
||||
commands:
|
||||
- go get ./...
|
||||
- go vet ./...
|
||||
- mkdir -p .build
|
||||
- go test -v -coverprofile=.build/coverage.out ./...
|
||||
- go tool cover -func=.build/coverage.out | tee .build/coverage.txt | awk '/^total:/ { gsub("%", "", $3); if ($3 + 0 < 80) { printf("Coverage %.1f%% is below 80%%\n", $3); exit 1 } }'
|
||||
- go test ./...
|
||||
- go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
- govulncheck -json ./... > vulncheck.json
|
||||
- govulncheck -v -json ./... > vulncheck.json
|
||||
|
|
@ -14,3 +14,6 @@ indent_size = 4
|
|||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.ps1]
|
||||
end_of_line = crlf
|
||||
|
|
|
|||
|
|
@ -13,5 +13,6 @@
|
|||
Makefile text eol=lf
|
||||
|
||||
# Keep native Windows script formats
|
||||
*.ps1 text eol=crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
|
|
|
|||
|
|
@ -14,8 +14,3 @@ The pre-commit hook validates for staged `.sh` files:
|
|||
|
||||
- executable bit in Git index (`100755`)
|
||||
- LF line endings (no CRLF)
|
||||
|
||||
The pre-commit hook also validates staged `.md` files with `markdownlint`:
|
||||
|
||||
- no `markdownlint` errors or problems
|
||||
- requires `markdownlint` CLI in PATH (for example via `npm install --global markdownlint-cli`)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ failed=0
|
|||
cr=$(printf '\r')
|
||||
|
||||
staged_shell_files=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.sh$' || true)
|
||||
staged_markdown_files=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.md$' || true)
|
||||
|
||||
for file in $staged_shell_files; do
|
||||
mode=$(git ls-files --stage -- "$file" | awk '{print $1}')
|
||||
|
|
@ -20,20 +19,6 @@ for file in $staged_shell_files; do
|
|||
fi
|
||||
done
|
||||
|
||||
if [ -n "$staged_markdown_files" ]; then
|
||||
if ! command -v markdownlint >/dev/null 2>&1; then
|
||||
echo "ERROR: markdownlint is required to validate staged Markdown files (.md)." >&2
|
||||
echo "Install with npm: npm install --global markdownlint-cli" >&2
|
||||
failed=1
|
||||
else
|
||||
# Validate the staged markdown files currently present in the working tree.
|
||||
# This keeps the hook simple and fast for standard project usage.
|
||||
if ! markdownlint $staged_markdown_files; then
|
||||
failed=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$failed" -ne 0 ]; then
|
||||
echo "Pre-commit check failed." >&2
|
||||
exit 1
|
||||
|
|
|
|||
|
|
@ -11,4 +11,3 @@ coverage.out
|
|||
Thumbs.db
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
|
|
|
|||
57
AGENTS.md
57
AGENTS.md
|
|
@ -1,20 +1,14 @@
|
|||
# AGENTS.md
|
||||
|
||||
## Purpose
|
||||
|
||||
This file defines the default working agreement for AI coding agents
|
||||
and contributors in this repository.
|
||||
This file defines the default working agreement for AI coding agents and contributors in this repository.
|
||||
|
||||
## Documentation Language
|
||||
|
||||
Project language is English. All documentation, issues, pull requests,
|
||||
commit messages, and code comments should be written in English.
|
||||
Project language is English. All documentation, issues, pull requests, commit messages, and code comments should be written in English.
|
||||
All newly created documentation in this project must be written in English.
|
||||
All project documentation files must be stored under `docs/`,
|
||||
except `README.md` and `AGENTS.md`.
|
||||
All project documentation files must be stored under `docs/`, except `README.md` and `AGENTS.md`.
|
||||
|
||||
## Working Principles
|
||||
|
||||
1. Keep changes minimal and focused on the requested task.
|
||||
2. Preserve existing public APIs unless a breaking change is explicitly requested.
|
||||
3. Prefer clear, maintainable code over clever shortcuts.
|
||||
|
|
@ -22,46 +16,28 @@ except `README.md` and `AGENTS.md`.
|
|||
5. Never add secrets, credentials, or tokens to the repository.
|
||||
|
||||
## Testing Expectations
|
||||
|
||||
1. Add or update tests for behavior changes.
|
||||
2. Keep tests deterministic and fast.
|
||||
3. Prefer table-driven tests where they improve readability.
|
||||
4. Run relevant tests locally before finishing changes.
|
||||
5. For Go projects, use `github.com/smartystreets/goconvey`
|
||||
as the standard test library.
|
||||
5. For Go projects, use `github.com/smartystreets/goconvey` as the standard test library.
|
||||
|
||||
## Build Artifacts and Reports
|
||||
|
||||
1. Builder logs and generated reports must be created under `.build/`.
|
||||
2. The `.build/` directory must be excluded from version control via `.gitignore`.
|
||||
|
||||
## Git and Script Standards
|
||||
|
||||
1. Shell scripts (`*.sh`) must use LF line endings.
|
||||
2. Shell scripts committed to the repository must be executable
|
||||
in Git index (mode `100755`).
|
||||
3. When adding a new shell script, set execute permissions
|
||||
before commit: `git add --chmod=+x path/to/script.sh`.
|
||||
|
||||
## Git Bash Execution Defaults
|
||||
|
||||
1. Repository maintenance scripts are executed with Git Bash shell on Windows.
|
||||
2. Default repository root is `~/git`.
|
||||
3. The repository root can be overridden via `GO_GIT_ROOT`.
|
||||
4. The standards repository defaults to `$ROOT_PATH/project-standards`.
|
||||
5. The standards repository path can be overridden via `GO_PROJECT_STANDARDS`.
|
||||
2. Shell scripts committed to the repository must be executable in Git index (mode `100755`).
|
||||
3. When adding a new shell script, set execute permissions before commit: `git add --chmod=+x path/to/script.sh`.
|
||||
|
||||
## Definition of Done (DoD)
|
||||
|
||||
### DoD Purpose
|
||||
|
||||
The Definition of Done defines the minimum quality bar
|
||||
for every completed change in this repository.
|
||||
### Purpose
|
||||
The Definition of Done defines the minimum quality bar for every completed change in this repository.
|
||||
|
||||
### Mandatory Criteria
|
||||
|
||||
1. Tests
|
||||
|
||||
- Every code change is covered by tests where applicable.
|
||||
- New functionality includes new tests.
|
||||
- Bug fixes include at least one regression test.
|
||||
|
|
@ -69,41 +45,28 @@ for every completed change in this repository.
|
|||
- Automated test coverage is at least 80%.
|
||||
|
||||
1. Functional documentation
|
||||
|
||||
- Implemented functionality is documented.
|
||||
- Public API-relevant changes are reflected in README and/or docs.
|
||||
|
||||
1. Documentation standards
|
||||
|
||||
- Documentation is written in English.
|
||||
- Documentation files are placed under `docs/`.
|
||||
- Exceptions: `README.md` and `AGENTS.md` remain at repository root.
|
||||
- Markdown files have no `markdownlint` errors or problems.
|
||||
|
||||
### Technical Completion Criteria
|
||||
|
||||
1. Build and test status
|
||||
|
||||
- The project builds successfully.
|
||||
- Relevant test commands run successfully.
|
||||
|
||||
1. No unresolved critical issues
|
||||
|
||||
- No new blocking errors are introduced.
|
||||
- Known non-blocking warnings are acceptable
|
||||
only if unrelated to the change or documented.
|
||||
|
||||
1. SonarQube status
|
||||
|
||||
- No SonarQube errors are present.
|
||||
- Known non-blocking warnings are acceptable only if unrelated to the change or documented.
|
||||
|
||||
1. Documentation structure
|
||||
|
||||
- Links to moved or newly added docs are valid.
|
||||
- Documentation structure remains consistent with project rules.
|
||||
|
||||
### Review Checklist (Quick)
|
||||
|
||||
- [ ] Change is implemented and meets acceptance criteria.
|
||||
- [ ] Tests were added/updated and pass.
|
||||
- [ ] Go tests use `github.com/smartystreets/goconvey`.
|
||||
|
|
@ -111,6 +74,4 @@ for every completed change in this repository.
|
|||
- [ ] Functionality is documented.
|
||||
- [ ] Documentation is in English.
|
||||
- [ ] Documentation is located under `docs/` (except `README.md` and `AGENTS.md`).
|
||||
- [ ] Markdown files have no `markdownlint` errors or problems.
|
||||
- [ ] No SonarQube errors are present.
|
||||
- [ ] No critical regressions found.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Go utility library
|
||||
<div style="text-align:left"><img src="https://www.yoorie.de/img/favicon_32.png"/></div>
|
||||
|
||||

|
||||
# Go utility library
|
||||
|
||||
[](https://drone.yoorie.de/go-lib/util)
|
||||
|
||||
|
|
@ -58,3 +58,5 @@ func main() {
|
|||
|
||||
---
|
||||
Copyright © 2023 yoorie.de
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@
|
|||
|
||||
## Purpose
|
||||
|
||||
This Definition of Done defines the minimum quality bar
|
||||
for every completed change in this repository.
|
||||
This Definition of Done defines the minimum quality bar for every completed change in this repository.
|
||||
|
||||
## Mandatory Criteria
|
||||
|
||||
1. Tests
|
||||
|
||||
- Every code change is covered by tests where applicable.
|
||||
- New functionality includes new tests.
|
||||
- Bug fixes include at least one regression test.
|
||||
|
|
@ -16,36 +14,25 @@ for every completed change in this repository.
|
|||
- Automated test coverage is at least 80%.
|
||||
|
||||
1. Functional documentation
|
||||
|
||||
- Implemented functionality is documented.
|
||||
- Public API-relevant changes are reflected in README and/or docs.
|
||||
|
||||
1. Documentation standards
|
||||
|
||||
- Documentation is written in English.
|
||||
- Documentation files are placed under `docs/`.
|
||||
- Exceptions: `README.md` and `AGENTS.md` remain at repository root.
|
||||
- Markdown files have no `markdownlint` errors or problems.
|
||||
|
||||
## Technical Completion Criteria
|
||||
|
||||
1. Build and test status
|
||||
|
||||
- The project builds successfully.
|
||||
- Relevant test commands run successfully.
|
||||
|
||||
1. No unresolved critical issues
|
||||
|
||||
- No new blocking errors are introduced.
|
||||
- Known non-blocking warnings are acceptable
|
||||
only if unrelated to the change or documented.
|
||||
|
||||
1. SonarQube status
|
||||
|
||||
- No SonarQube errors are present.
|
||||
- Known non-blocking warnings are acceptable only if unrelated to the change or documented.
|
||||
|
||||
1. Documentation links and structure
|
||||
|
||||
- Links to moved or newly added docs are valid.
|
||||
- Documentation structure remains consistent with project rules.
|
||||
|
||||
|
|
@ -58,6 +45,4 @@ for every completed change in this repository.
|
|||
- [ ] Functionality is documented.
|
||||
- [ ] Documentation is in English.
|
||||
- [ ] Documentation is located under `docs/` (except `README.md` and `AGENTS.md`).
|
||||
- [ ] Markdown files have no `markdownlint` errors or problems.
|
||||
- [ ] No SonarQube errors are present.
|
||||
- [ ] No critical regressions found.
|
||||
|
|
|
|||
|
|
@ -13,16 +13,6 @@ import (
|
|||
|
||||
var (
|
||||
allocateAndInitializeSid = windows.AllocateAndInitializeSid
|
||||
allocateAdminGroupSid = func(sid **windows.SID) error {
|
||||
return allocateAndInitializeSid(
|
||||
&windows.SECURITY_NT_AUTHORITY,
|
||||
2,
|
||||
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
||||
windows.DOMAIN_ALIAS_RID_ADMINS,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
sid,
|
||||
)
|
||||
}
|
||||
freeSid = windows.FreeSid
|
||||
tokenIsMember = func(token windows.Token, sid *windows.SID) (bool, error) { return token.IsMember(sid) }
|
||||
fatalf = log.Fatalf
|
||||
|
|
@ -37,7 +27,13 @@ func IsSuperUser() bool {
|
|||
// official windows documentation. The Go API for this is a
|
||||
// direct wrap around the official C++ API.
|
||||
// See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
|
||||
err := allocateAdminGroupSid(&sid)
|
||||
err := allocateAndInitializeSid(
|
||||
&windows.SECURITY_NT_AUTHORITY,
|
||||
2,
|
||||
windows.SECURITY_BUILTIN_DOMAIN_RID,
|
||||
windows.DOMAIN_ALIAS_RID_ADMINS,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
&sid)
|
||||
if err != nil {
|
||||
fatalf("SID Error: %s", err)
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -11,21 +11,33 @@ import (
|
|||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func TestIsSuperUserSidAllocationError(t *testing.T) {
|
||||
func TestIsSuperUser_SidAllocationError(t *testing.T) {
|
||||
Convey("IsSuperUser should return false when SID allocation fails", t, func() {
|
||||
origAllocate := allocateAdminGroupSid
|
||||
origAllocate := allocateAndInitializeSid
|
||||
origFatalf := fatalf
|
||||
defer func() {
|
||||
allocateAdminGroupSid = origAllocate
|
||||
allocateAndInitializeSid = origAllocate
|
||||
fatalf = origFatalf
|
||||
}()
|
||||
|
||||
allocateAdminGroupSid = func(_ **windows.SID) error {
|
||||
allocateAndInitializeSid = func(
|
||||
authority *windows.SidIdentifierAuthority,
|
||||
subAuthorityCount byte,
|
||||
subAuthority0 uint32,
|
||||
subAuthority1 uint32,
|
||||
subAuthority2 uint32,
|
||||
subAuthority3 uint32,
|
||||
subAuthority4 uint32,
|
||||
subAuthority5 uint32,
|
||||
subAuthority6 uint32,
|
||||
subAuthority7 uint32,
|
||||
sid **windows.SID,
|
||||
) error {
|
||||
return errors.New("forced sid allocation error")
|
||||
}
|
||||
|
||||
fatalCalled := false
|
||||
fatalf = func(_ string, _ ...interface{}) {
|
||||
fatalf = func(format string, v ...interface{}) {
|
||||
fatalCalled = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,217 @@
|
|||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$RootPath = "C:\Users\stefan\git",
|
||||
[string]$StandardsRepoPath = "C:\Users\stefan\git\project-standards",
|
||||
[string[]]$Exclude = @('project-standards'),
|
||||
[switch]$CheckOnly,
|
||||
[switch]$Watch,
|
||||
[int]$IntervalSeconds = 60
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$agentsTemplate = Join-Path $StandardsRepoPath 'templates/AGENTS.base.md'
|
||||
$dodTemplate = Join-Path $StandardsRepoPath 'templates/DEFINITION_OF_DONE.base.md'
|
||||
$gitIgnoreTemplate = Join-Path $StandardsRepoPath 'templates/.gitignore.base'
|
||||
$gitAttributesTemplate = Join-Path $StandardsRepoPath 'templates/.gitattributes.base'
|
||||
$editorConfigTemplate = Join-Path $StandardsRepoPath 'templates/.editorconfig.base'
|
||||
$preCommitHookTemplate = Join-Path $StandardsRepoPath 'templates/pre-commit.base.sh'
|
||||
$hooksReadmeTemplate = Join-Path $StandardsRepoPath 'templates/.githooks.README.base.md'
|
||||
|
||||
if (-not (Test-Path -Path $agentsTemplate -PathType Leaf)) {
|
||||
throw "AGENTS template not found: $agentsTemplate"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $dodTemplate -PathType Leaf)) {
|
||||
throw "DoD template not found: $dodTemplate"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $gitIgnoreTemplate -PathType Leaf)) {
|
||||
throw "gitignore template not found: $gitIgnoreTemplate"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $gitAttributesTemplate -PathType Leaf)) {
|
||||
throw "gitattributes template not found: $gitAttributesTemplate"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $editorConfigTemplate -PathType Leaf)) {
|
||||
throw "editorconfig template not found: $editorConfigTemplate"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $preCommitHookTemplate -PathType Leaf)) {
|
||||
throw "pre-commit hook template not found: $preCommitHookTemplate"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $hooksReadmeTemplate -PathType Leaf)) {
|
||||
throw "hooks readme template not found: $hooksReadmeTemplate"
|
||||
}
|
||||
|
||||
if ($IntervalSeconds -lt 5) {
|
||||
throw 'IntervalSeconds must be >= 5.'
|
||||
}
|
||||
|
||||
function Get-ContentHashOrMissing {
|
||||
param([string]$Path)
|
||||
|
||||
if (-not (Test-Path -Path $Path -PathType Leaf)) {
|
||||
return '__MISSING__'
|
||||
}
|
||||
|
||||
return (Get-FileHash -Path $Path -Algorithm SHA256).Hash
|
||||
}
|
||||
|
||||
function Ensure-FileFromTemplate {
|
||||
param(
|
||||
[string]$Template,
|
||||
[string]$Target,
|
||||
[switch]$OnlyCheck
|
||||
)
|
||||
|
||||
$templateHash = Get-ContentHashOrMissing -Path $Template
|
||||
$targetHash = Get-ContentHashOrMissing -Path $Target
|
||||
|
||||
if ($templateHash -eq $targetHash) {
|
||||
return 'ok'
|
||||
}
|
||||
|
||||
if ($OnlyCheck) {
|
||||
return 'drift'
|
||||
}
|
||||
|
||||
$parent = Split-Path -Parent $Target
|
||||
if (-not (Test-Path -Path $parent -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path $parent | Out-Null
|
||||
}
|
||||
|
||||
Copy-Item -Path $Template -Destination $Target -Force
|
||||
return 'updated'
|
||||
}
|
||||
|
||||
function Ensure-GitIgnoreEntriesFromTemplate {
|
||||
param(
|
||||
[string]$TemplatePath,
|
||||
[string]$GitIgnorePath,
|
||||
[switch]$OnlyCheck
|
||||
)
|
||||
|
||||
$requiredEntries = Get-Content -Path $TemplatePath | Where-Object {
|
||||
$_.Trim() -and -not $_.Trim().StartsWith('#')
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $GitIgnorePath -PathType Leaf)) {
|
||||
if ($OnlyCheck) {
|
||||
return 'drift'
|
||||
}
|
||||
|
||||
New-Item -ItemType File -Path $GitIgnorePath | Out-Null
|
||||
}
|
||||
|
||||
$lines = Get-Content -Path $GitIgnorePath
|
||||
|
||||
$missingEntry = $false
|
||||
foreach ($entry in $requiredEntries) {
|
||||
if ($lines -contains $entry) {
|
||||
continue
|
||||
}
|
||||
|
||||
$missingEntry = $true
|
||||
if (-not $OnlyCheck) {
|
||||
Add-Content -Path $GitIgnorePath -Value $entry
|
||||
$lines += $entry
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $missingEntry) {
|
||||
return 'ok'
|
||||
}
|
||||
|
||||
if ($OnlyCheck) {
|
||||
return 'drift'
|
||||
}
|
||||
|
||||
return 'updated'
|
||||
}
|
||||
|
||||
function Invoke-ReconcileOnce {
|
||||
param([switch]$OnlyCheck)
|
||||
|
||||
$resolvedRoot = (Resolve-Path -Path $RootPath -ErrorAction Stop).Path
|
||||
$repos = Get-ChildItem -Path $resolvedRoot -Directory | Where-Object {
|
||||
$Exclude -notcontains $_.Name
|
||||
}
|
||||
|
||||
$summary = [ordered]@{
|
||||
scanned = 0
|
||||
updated = 0
|
||||
drift = 0
|
||||
}
|
||||
|
||||
foreach ($repo in $repos) {
|
||||
$repoPath = $repo.FullName
|
||||
$agentsTarget = Join-Path $repoPath 'AGENTS.md'
|
||||
$dodTarget = Join-Path (Join-Path $repoPath 'docs') 'DEFINITION_OF_DONE.md'
|
||||
$gitIgnoreTarget = Join-Path $repoPath '.gitignore'
|
||||
$gitAttributesTarget = Join-Path $repoPath '.gitattributes'
|
||||
$editorConfigTarget = Join-Path $repoPath '.editorconfig'
|
||||
$preCommitHookTarget = Join-Path (Join-Path $repoPath '.githooks') 'pre-commit'
|
||||
$hooksReadmeTarget = Join-Path (Join-Path $repoPath '.githooks') 'README.md'
|
||||
|
||||
$summary.scanned++
|
||||
|
||||
$agentsState = Ensure-FileFromTemplate -Template $agentsTemplate -Target $agentsTarget -OnlyCheck:$OnlyCheck
|
||||
$dodState = Ensure-FileFromTemplate -Template $dodTemplate -Target $dodTarget -OnlyCheck:$OnlyCheck
|
||||
$gitAttributesState = Ensure-FileFromTemplate -Template $gitAttributesTemplate -Target $gitAttributesTarget -OnlyCheck:$OnlyCheck
|
||||
$editorConfigState = Ensure-FileFromTemplate -Template $editorConfigTemplate -Target $editorConfigTarget -OnlyCheck:$OnlyCheck
|
||||
$preCommitHookState = Ensure-FileFromTemplate -Template $preCommitHookTemplate -Target $preCommitHookTarget -OnlyCheck:$OnlyCheck
|
||||
$hooksReadmeState = Ensure-FileFromTemplate -Template $hooksReadmeTemplate -Target $hooksReadmeTarget -OnlyCheck:$OnlyCheck
|
||||
$gitIgnoreState = Ensure-GitIgnoreEntriesFromTemplate -TemplatePath $gitIgnoreTemplate -GitIgnorePath $gitIgnoreTarget -OnlyCheck:$OnlyCheck
|
||||
|
||||
if (
|
||||
$agentsState -eq 'updated' -or
|
||||
$dodState -eq 'updated' -or
|
||||
$gitAttributesState -eq 'updated' -or
|
||||
$editorConfigState -eq 'updated' -or
|
||||
$preCommitHookState -eq 'updated' -or
|
||||
$hooksReadmeState -eq 'updated' -or
|
||||
$gitIgnoreState -eq 'updated'
|
||||
) {
|
||||
$summary.updated++
|
||||
Write-Host "UPDATED: $repoPath"
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
$agentsState -eq 'drift' -or
|
||||
$dodState -eq 'drift' -or
|
||||
$gitAttributesState -eq 'drift' -or
|
||||
$editorConfigState -eq 'drift' -or
|
||||
$preCommitHookState -eq 'drift' -or
|
||||
$hooksReadmeState -eq 'drift' -or
|
||||
$gitIgnoreState -eq 'drift'
|
||||
) {
|
||||
$summary.drift++
|
||||
Write-Host "DRIFT: $repoPath"
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "OK: $repoPath"
|
||||
}
|
||||
|
||||
Write-Host "Summary -> scanned=$($summary.scanned), updated=$($summary.updated), drift=$($summary.drift)"
|
||||
return $summary
|
||||
}
|
||||
|
||||
if ($Watch) {
|
||||
while ($true) {
|
||||
$now = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Write-Host "[$now] Reconciling project standards..."
|
||||
[void](Invoke-ReconcileOnce -OnlyCheck:$CheckOnly)
|
||||
Start-Sleep -Seconds $IntervalSeconds
|
||||
}
|
||||
}
|
||||
|
||||
$result = Invoke-ReconcileOnce -OnlyCheck:$CheckOnly
|
||||
if ($CheckOnly -and $result.drift -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
ROOT_PATH="${GO_GIT_ROOT:-$HOME/git}"
|
||||
STANDARDS_REPO=""
|
||||
STANDARDS_REPO_SET=0
|
||||
ROOT_PATH="/c/Users/stefan/git"
|
||||
STANDARDS_REPO="/c/Users/stefan/git/project-standards"
|
||||
EXCLUDE_NAME="project-standards"
|
||||
CHECK_ONLY=0
|
||||
WATCH=0
|
||||
|
|
@ -22,14 +21,6 @@ Options:
|
|||
--watch Continuously scan and reconcile
|
||||
--interval <seconds> Watch interval in seconds (default: 60)
|
||||
-h, --help Show this help
|
||||
|
||||
Environment:
|
||||
GO_GIT_ROOT Overrides default root path (default: ~/git)
|
||||
GO_PROJECT_STANDARDS Overrides standards repository path
|
||||
|
||||
Default resolution:
|
||||
ROOT_PATH defaults to GO_GIT_ROOT or ~/git.
|
||||
STANDARDS_REPO defaults to GO_PROJECT_STANDARDS or $ROOT_PATH/project-standards.
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +32,6 @@ while [ "$#" -gt 0 ]; do
|
|||
;;
|
||||
--standards-repo)
|
||||
STANDARDS_REPO=$2
|
||||
STANDARDS_REPO_SET=1
|
||||
shift 2
|
||||
;;
|
||||
--exclude)
|
||||
|
|
@ -72,14 +62,6 @@ while [ "$#" -gt 0 ]; do
|
|||
esac
|
||||
done
|
||||
|
||||
if [ "$STANDARDS_REPO_SET" -ne 1 ]; then
|
||||
if [ -n "${GO_PROJECT_STANDARDS:-}" ]; then
|
||||
STANDARDS_REPO="$GO_PROJECT_STANDARDS"
|
||||
else
|
||||
STANDARDS_REPO="$ROOT_PATH/project-standards"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$INTERVAL" -lt 5 ]; then
|
||||
echo "interval must be >= 5" >&2
|
||||
exit 1
|
||||
|
|
@ -180,7 +162,6 @@ ensure_gitignore_entries_from_template() {
|
|||
fi
|
||||
|
||||
while IFS= read -r entry; do
|
||||
entry=$(printf '%s' "$entry" | tr -d '\r')
|
||||
[ -n "$entry" ] || continue
|
||||
case "$entry" in
|
||||
\#*)
|
||||
|
|
@ -188,7 +169,7 @@ ensure_gitignore_entries_from_template() {
|
|||
;;
|
||||
esac
|
||||
|
||||
if tr -d '\r' < "$gitignore_path" | grep -Fqx "$entry"; then
|
||||
if grep -Fqx "$entry" "$gitignore_path"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
|
|
|
|||
1
utils.go
1
utils.go
|
|
@ -34,6 +34,7 @@ func joiningSlash(elem []string) string {
|
|||
}
|
||||
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
filepath.Join(a, b)
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
switch {
|
||||
|
|
|
|||
|
|
@ -23,20 +23,12 @@ func TestFileExists(t *testing.T) {
|
|||
|
||||
func TestJoiningSlash(t *testing.T) {
|
||||
Convey("JoiningSlash should combine URL-like segments safely", t, func() {
|
||||
const (
|
||||
baseURL = "http://my.tld"
|
||||
docsURL = "http://my.tld/docs"
|
||||
expectedRoot = "http://my.tld/bla/blub"
|
||||
expectedDocs = "http://my.tld/docs/bla/blub"
|
||||
expectedDocsS = "http://my.tld/docs/bla/blub/"
|
||||
)
|
||||
|
||||
So(JoiningSlash(docsURL+"/", "bla/", "blub/"), ShouldEqual, expectedDocsS)
|
||||
So(JoiningSlash(baseURL, "bla", "blub"), ShouldEqual, expectedRoot)
|
||||
So(JoiningSlash(baseURL+"/", "bla", "blub"), ShouldEqual, expectedRoot)
|
||||
So(JoiningSlash(baseURL, "bla/", "blub"), ShouldEqual, expectedRoot)
|
||||
So(JoiningSlash(docsURL, "bla/", "blub"), ShouldEqual, expectedDocs)
|
||||
So(JoiningSlash(docsURL+"/", "bla/", "blub"), ShouldEqual, expectedDocs)
|
||||
So(JoiningSlash("http://my.tld/docs/", "bla/", "blub/"), ShouldEqual, "http://my.tld/docs/bla/blub/")
|
||||
So(JoiningSlash("http://my.tld", "bla", "blub"), ShouldEqual, "http://my.tld/bla/blub")
|
||||
So(JoiningSlash("http://my.tld/", "bla", "blub"), ShouldEqual, "http://my.tld/bla/blub")
|
||||
So(JoiningSlash("http://my.tld", "bla/", "blub"), ShouldEqual, "http://my.tld/bla/blub")
|
||||
So(JoiningSlash("http://my.tld/docs", "bla/", "blub"), ShouldEqual, "http://my.tld/docs/bla/blub")
|
||||
So(JoiningSlash("http://my.tld/docs/", "bla/", "blub"), ShouldEqual, "http://my.tld/docs/bla/blub")
|
||||
So(JoiningSlash("", "api", "v1"), ShouldEqual, "api/v1")
|
||||
So(JoiningSlash("", "", ""), ShouldEqual, "")
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue