Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "GENEALOGIX Spec",
"image": "mcr.microsoft.com/devcontainers/go:1.26",
"image": "mcr.microsoft.com/devcontainers/go:1.27",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "24"
Expand Down
2 changes: 1 addition & 1 deletion .golangci-lint-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.12.2
v2.13.2
4 changes: 1 addition & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,7 @@ linters:
nolintlint:
require-explanation: true
require-specific: true
# allow-unused deferred: the unused-directive check surfaces 15 pre-existing
# redundant //nolint:mnd directives, cleaned up separately in #902.
allow-unused: true
allow-unused: false

exclusions:
generated: lax
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]

### Changed

- **Toolchain bump to Go 1.27 and golangci-lint v2.13.2, with `nolintlint` unused-directive enforcement** — `go.mod` and `ci-tools/go.mod` now declare `go 1.27.0` / `toolchain go1.27.1`, the devcontainer image moves to `go:1.27`, and `.golangci-lint-version` pins `v2.13.2`. The 35 `//nolint` directives that no longer suppressed anything (mostly `//nolint:mnd` on numbers already in `ignored-numbers`, plus stale `errcheck`/`nilerr`/`goconst`/`revive`/`gocyclo` directives) were removed, and `nolintlint`'s `allow-unused` is set to `false` so redundant suppressions are caught from now on. Contributors need Go 1.27+, and so do downstream projects importing `github.com/genealogix/glx/go-glx`. (#902)

### Removed

- **Retired the `auto-resolve-conflicts.yml` workflow in favor of git's native `union` merge driver** — The workflow re-merged conflicting PR branches on a runner and then stripped conflict markers from `CHANGELOG.md` with `sed` plus an `awk` header-dedup pass; since it landed in #82 it needed a string of follow-ups (#194, #341, #592, #601, #602, #657, #1054, #1107) to stay correct. `.gitattributes` now maps `CHANGELOG.md merge=union`, so git itself keeps both sides during the merge — no markers to strip, no second workflow, and no `workflow_run` chaining. `union` is a git built-in, so unlike the `merge=glx` driver for `.glx` files it needs no per-clone `git config`; it applies in every clone. It applies to local merges and rebases only: GitHub's server-side merges API (the "Update branch" button and `auto-update-branches.yml`) does not honor `.gitattributes` merge drivers, so a CHANGELOG-only conflict reported on GitHub is resolved by merging `main` locally and pushing. `CONTRIBUTING.md` and the root `CLAUDE.md` document both halves, plus the one case that still needs a human eye — union keeps both sides blindly, so merging a `main` that has just cut a release can land a branch entry inside the released section. `auto-update-branches.yml` is unchanged. Closes #888
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ What to expect when you pick one up:

### Prerequisites

- **Go 1.26+** ([install](https://golang.org/doc/install)) — the project uses Go 1.26 in go.mod
- **Go 1.27+** ([install](https://golang.org/doc/install)) — the project uses Go 1.27 in go.mod
- **Git** ([install](https://git-scm.com/downloads))
- **Node.js** — for website builds (`npm install` in `website/`) and schema validation (`npm ci --prefix specification`)

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ lint-codeowners: ## Verify every file is matched by a .github/CODEOWNERS rule
fi; \
echo "CODEOWNERS coverage OK"

fix: ## Run Go 1.26 modernizers on codebase
fix: ## Run Go 1.27 modernizers on codebase
go fix ./...

fix-diff: ## Preview Go 1.26 modernizer changes without applying
fix-diff: ## Preview Go 1.27 modernizer changes without applying
go fix -diff ./...

## Testing
Expand Down
4 changes: 2 additions & 2 deletions ci-tools/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/genealogix/glx/ci-tools

go 1.26.0
go 1.27.0

toolchain go1.26.1
toolchain go1.27.1

tool (
github.com/hmarr/codeowners/cmd/codeowners
Expand Down
4 changes: 2 additions & 2 deletions glx/add_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ func finalizeAdd(io *IOStreams, opts *addCommonOptions, ctx *addContext, entityT
io.Printf("Adding %s %s\n", entityType, entityID)
if opts.DryRun {
io.Println("(dry run — no files written)")
fmt.Fprintln(io.MachineOut, entityID) //nolint:errcheck // CLI output
fmt.Fprintln(io.MachineOut, entityID)

return nil
}
Expand All @@ -441,7 +441,7 @@ func finalizeAdd(io *IOStreams, opts *addCommonOptions, ctx *addContext, entityT
return err
}

fmt.Fprintln(io.MachineOut, entityID) //nolint:errcheck // CLI output
fmt.Fprintln(io.MachineOut, entityID)

return nil
}
Expand Down
4 changes: 2 additions & 2 deletions glx/archive_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ func loadCache(root string) (*glxlib.GLXFile, *CacheHeader, error) {
if err != nil {
return nil, nil, err
}
defer f.Close() //nolint:errcheck // read-only handle
defer f.Close()

var archive glxlib.GLXFile
if err := dec.Decode(&archive); err != nil {
Expand Down Expand Up @@ -548,7 +548,7 @@ func tryLoadFreshCache(root string) (*glxlib.GLXFile, []string, bool) {
if err != nil {
return nil, nil, false
}
defer f.Close() //nolint:errcheck // read-only handle
defer f.Close()

if !cacheIsFresh(root, header) {
return nil, nil, false
Expand Down
2 changes: 1 addition & 1 deletion glx/cache_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func cleanCache(io *IOStreams, path string) error {
if err := os.Remove(cacheDir(root)); err != nil && !os.IsNotExist(err) {
// Non-empty or permission issue — leave it; the cache file is gone, which
// is what the user asked for.
return nil //nolint:nilerr // directory cleanup is best-effort
return nil
}

return nil
Expand Down
4 changes: 2 additions & 2 deletions glx/cli_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -1807,7 +1807,7 @@ the driver to fall back to git's text merge — standard <<<<<<< markers
land in <ours>, and a diagnostic summary listing both sides' values
(with assertion confidence and citations when applicable) is written to
stderr.`,
Args: cobra.RangeArgs(3, 4), //nolint:mnd // git merge-driver signature is %O %A %B [%P]
Args: cobra.RangeArgs(3, 4), // git merge-driver signature is %O %A %B [%P]
RunE: runMergeDriverCmd,
}

Expand All @@ -1817,7 +1817,7 @@ func runMergeDriverCmd(_ *cobra.Command, args []string) error {
OursPath: args[1],
TheirsPath: args[2],
}
if len(args) >= 4 { //nolint:mnd // %P is git's 4th positional arg
if len(args) >= 4 { // %P is git's 4th positional arg
in.OrigPath = args[3]
} else {
in.OrigPath = in.OursPath
Expand Down
2 changes: 0 additions & 2 deletions glx/coverage_state_census.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import (
// Note: some state census years overlap with federal census years (e.g.,
// Mississippi 1860). The matching logic requires a state-specific signal
// to avoid confusing state and federal censuses.
//
//nolint:goconst // state names are data entries, not magic strings
var stateCensusYears = map[string][]int{
"Wisconsin": {1855, 1865, 1875, 1885, 1895, 1905},
"New York": {1825, 1835, 1845, 1855, 1865, 1875, 1892, 1905, 1915, 1925},
Expand Down
2 changes: 1 addition & 1 deletion glx/e2e/harness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func buildAndRun(m *testing.M) (int, error) {
if err != nil {
return 0, fmt.Errorf("creating temp dir for binary: %w", err)
}
defer os.RemoveAll(dir) //nolint:errcheck // best-effort cleanup
defer os.RemoveAll(dir)

bin := filepath.Join(dir, "glx")
if runtime.GOOS == "windows" {
Expand Down
2 changes: 1 addition & 1 deletion glx/evidence_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ func printEvidenceJSON(io *IOStreams, r *EvidenceReport) error {
if err != nil {
return fmt.Errorf("failed to marshal evidence report: %w", err)
}
fmt.Fprintln(io.MachineOut, string(data)) //nolint:errcheck // CLI output
fmt.Fprintln(io.MachineOut, string(data))

return nil
}
Expand Down
6 changes: 3 additions & 3 deletions glx/iostreams.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,16 @@ func TestIOStreams() (*IOStreams, *bytes.Buffer, *bytes.Buffer) {
// sequences. MachineOut is not written through this method and is left
// byte-faithful.
func (s *IOStreams) Printf(format string, args ...any) {
fmt.Fprint(s.Out, sanitizeForTerminal(fmt.Sprintf(format, args...))) //nolint:errcheck // CLI output
fmt.Fprint(s.Out, sanitizeForTerminal(fmt.Sprintf(format, args...)))
}

// Println writes a line to the standard output stream, sanitized as in Printf.
func (s *IOStreams) Println(msg string) {
fmt.Fprintln(s.Out, sanitizeForTerminal(msg)) //nolint:errcheck // CLI output
fmt.Fprintln(s.Out, sanitizeForTerminal(msg))
}

// Errorf writes a formatted string to the error output stream, sanitized as
// in Printf.
func (s *IOStreams) Errorf(format string, args ...any) {
fmt.Fprint(s.ErrOut, sanitizeForTerminal(fmt.Sprintf(format, args...))) //nolint:errcheck // CLI output
fmt.Fprint(s.ErrOut, sanitizeForTerminal(fmt.Sprintf(format, args...)))
}
2 changes: 1 addition & 1 deletion glx/migrations_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ func printMigrationsJSON(io *IOStreams, report any) error {
if err != nil {
return fmt.Errorf("failed to marshal migrations report: %w", err)
}
fmt.Fprintln(io.MachineOut, string(data)) //nolint:errcheck // CLI output
fmt.Fprintln(io.MachineOut, string(data))

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion glx/proof_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,7 @@ func printProofJSON(io *IOStreams, result *proofResult) error {
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
fmt.Fprintln(io.MachineOut, string(data)) //nolint:errcheck // CLI output
fmt.Fprintln(io.MachineOut, string(data))

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion glx/publish_media.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ func (r *mediaResolver) copyMedia(srcPath, uri string) (string, error) {
// refuses symlinks that escape baseDir); either failure is "missing".
src, err := openWithin(r.baseDir, srcPath)
if err != nil {
return "", nil //nolint:nilerr // missing media is non-fatal
return "", nil
}
defer func() { _ = src.Close() }()

Expand Down
2 changes: 1 addition & 1 deletion glx/report_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type reportData struct {
}

// assertionSummary is a short description of an assertion for display.
type assertionSummary struct { //nolint:revive // SubjectType field name is part of the JSON wire format
type assertionSummary struct {
ID string
SubjectType glxlib.EntityType
SubjectID string
Expand Down
8 changes: 5 additions & 3 deletions glx/robustio_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import (

// errSharingViolation is syscall.Errno(32), equivalent to
// internal/syscall/windows.ERROR_SHARING_VIOLATION which is not importable.
const errSharingViolation syscall.Errno = 32 //nolint:mnd // Windows error code
const errSharingViolation syscall.Errno = 32

const retryTimeout = 2000 * time.Millisecond //nolint:mnd // matches Go toolchain robustio
// retryTimeout matches the Go toolchain's internal robustio budget.
const retryTimeout = 2000 * time.Millisecond

const maxSleep = 500 * time.Millisecond //nolint:mnd // cap per-retry sleep
// maxSleep caps the per-retry sleep.
const maxSleep = 500 * time.Millisecond

// robustRename is like os.Rename but retries on transient Windows errors.
//
Expand Down
4 changes: 1 addition & 3 deletions go-glx/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,9 +562,7 @@ var AllEntityTypes = []EntityType{
// they participate in entity IDs (e.g., `research-log-john-smith`), which are
// constrained to `[a-zA-Z0-9-]{1,64}`. This map IS the source of truth — the
// values are inherently string literals tied to the entity-type vocabulary,
// so goconst's "extract a constant" suggestion is misplaced here.
//
//nolint:goconst // the literal values are the source of truth for singular names
// not constants to be extracted.
var entityTypeSingular = map[EntityType]string{
EntityTypePersons: "person",
EntityTypeRelationships: "relationship",
Expand Down
2 changes: 1 addition & 1 deletion go-glx/date_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (ds DateString) Parse() (glxdate.Date, error) {
// range ("TO 1950"), whose end year is the only one present. A BCE year is
// negative.
func (ds DateString) Year() int {
d, _ := glxdate.Parse(string(ds)) //nolint:errcheck // best-effort year is defined even for non-canonical dates
d, _ := glxdate.Parse(string(ds))

return d.Year()
}
2 changes: 1 addition & 1 deletion go-glx/duplicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func FindCrossArchiveDuplicates(dest, src *GLXFile, opts DuplicateOptions) (*Dup
results = append(results, DuplicatePair{
PersonA: pair[0],
PersonB: pair[1],
Score: math.Round(score*100) / 100, //nolint:mnd // round to 2 decimal places
Score: math.Round(score*100) / 100,
Signals: signals,
})
}
Expand Down
12 changes: 6 additions & 6 deletions go-glx/gedcom_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func DecodeGEDCOMBlob(blobText string) ([]byte, error) {
}

result := make([]byte, 0, len(cleaned)*3/4)
fullGroups := (len(cleaned) / 4) * 4 //nolint:mnd // 4 chars per group
fullGroups := (len(cleaned) / 4) * 4 // 4 chars per group

for i := 0; i < fullGroups; i += 4 {
// Validate each character is in valid GEDCOM BLOB range (0x2E '.' to 0x6D 'm')
Expand All @@ -75,7 +75,7 @@ func DecodeGEDCOMBlob(blobText string) ([]byte, error) {
b3 := cleaned[i+2] - '.'
b4 := cleaned[i+3] - '.'

//nolint:mnd // well-known base64 bit shifts
// Well-known base64 bit shifts.
result = append(result,
(b1<<2)|(b2>>4),
(b2<<4)|(b3>>2),
Expand All @@ -96,14 +96,14 @@ func DecodeGEDCOMBlob(blobText string) ([]byte, error) {
if trailing == 1 {
return nil, fmt.Errorf("%w: %d (trailing single character cannot encode a full byte)", ErrGEDCOMBlobLength, len(cleaned))
}
if trailing >= 2 { //nolint:mnd // trailing group sizes
if trailing >= 2 { // a 2- or 3-char trailing group
b1 := cleaned[fullGroups] - '.'
b2 := cleaned[fullGroups+1] - '.'
result = append(result, (b1<<2)|(b2>>4)) //nolint:mnd // well-known base64 bit shifts
result = append(result, (b1<<2)|(b2>>4)) // well-known base64 bit shifts

if trailing == 3 { //nolint:mnd // 3-char trailing group
if trailing == 3 { // 3-char trailing group carries a second byte
b3 := cleaned[fullGroups+2] - '.'
result = append(result, (b2<<4)|(b3>>2)) //nolint:mnd // well-known base64 bit shifts
result = append(result, (b2<<4)|(b3>>2))
}
}

Expand Down
2 changes: 1 addition & 1 deletion go-glx/gedcom_place.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ func inferPlaceType(name string, level int) string {
case 2:
// Third level - likely state/province
return PlaceTypeState
case 3: //nolint:mnd // fourth hierarchy level = country
case 3:
// Fourth level - likely country
return PlaceTypeCountry
default:
Expand Down
8 changes: 4 additions & 4 deletions go-glx/glxdate/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func isPartialMonth(tokens []string) bool {
_, ok := MonthNumber(tokens[0])

return ok
case 2: //nolint:mnd // DD MONTH
case 2: // DD MONTH
_, ok := MonthNumber(tokens[1])

return ok && dayToken(tokens[0]) > 0
Expand Down Expand Up @@ -410,7 +410,7 @@ func parsePoint(cal Calendar, tokens []string) point {
// structured body, returning the remaining tokens and the era token as
// written ("" when absent). A body that is only an era token is left alone.
func splitEra(tokens []string) ([]string, string) {
if len(tokens) < 2 { //nolint:mnd // a date and its era
if len(tokens) < 2 { // an era split needs a date and its era
return tokens, ""
}
last := tokens[len(tokens)-1]
Expand Down Expand Up @@ -464,11 +464,11 @@ func parseNamedMonth(tokens []string, p *point) bool {
var month, year, day int
var ok bool
switch len(toks) {
case 2: //nolint:mnd // MONTH YYYY
case 2: // MONTH YYYY
month, ok = MonthNumber(toks[0])
year = yearToken(toks[1])
p.precision = PrecisionMonth
case 3: //nolint:mnd // DD MONTH YYYY or MONTH DD, YYYY
case 3: // DD MONTH YYYY or MONTH DD, YYYY
year = yearToken(toks[2])
if month, ok = MonthNumber(toks[1]); ok {
day = dayToken(toks[0])
Expand Down
2 changes: 0 additions & 2 deletions go-glx/merge3.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,6 @@ func tagConflicts(entityType EntityType, id string, conflicts []Merge3Conflict)
// with a null value" — git's merge can land us with a map literal that lists
// an ID but no body (e.g. an empty `persons:` entry under YAML serialization
// quirks), and we never want to emit `persons: {p1: null}` back out.
//
//nolint:gocyclo // 7-case finite presence enumeration; splitting further hurts readability.
func merge3EntityID[V any](
entityType EntityType, id string,
base, ours, theirs map[string]*V,
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/genealogix/glx

go 1.26.0
go 1.27.0

toolchain go1.26.1
toolchain go1.27.1

require (
github.com/brianvoe/gofakeit/v7 v7.17.0
Expand Down
Loading