pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/pkgtools/pkglint



Module Name:    pkgsrc
Committed By:   rillig
Date:           Sun Jan 29 13:36:32 UTC 2023

Modified Files:
        pkgsrc/pkgtools/pkglint: Makefile
        pkgsrc/pkgtools/pkglint/files: alternatives.go check_test.go
            files_test.go homepage.go line.go logging_test.go
            mkcondsimplifier.go mklexer.go mkline.go mkline_test.go
            mklinechecker.go mklinechecker_test.go mkparser.go mkshparser.go
            mkshtypes.go mktypes.go package.go package_test.go path.go
            pkglint.go pkglint_test.go pkgsrc.go pkgsrc_test.go plist.go
            scope.go shell.go shtokenizer.go shtypes.go substcontext_test.go
            tools.go util.go var.go varalignblock.go varalignblock_test.go
            vardefs.go vartype.go vartypecheck.go vartypecheck_test.go
        pkgsrc/pkgtools/pkglint/files/getopt: getopt.go
        pkgsrc/pkgtools/pkglint/files/intqa: qa.go
        pkgsrc/pkgtools/pkglint/files/makepat: pat.go
        pkgsrc/pkgtools/pkglint/files/trace: tracing.go
Added Files:
        pkgsrc/pkgtools/pkglint/files: changes.go changes_test.go

Log Message:
pkgtools/pkglint: update to 22.4.0

Changes since 22.3.2:

Numeric comparisons of _PYTHON_VERSION generate an error since that
variable can have the value 'none', which is not numeric.  Furthermore,
the variable is from the internal namespace and thus should not be used
by packages at all.

Warnings about COMPILER_RPATH_FLAG no longer suggest different and
conflicting replacements.

Cleanup: Handling of doc/CHANGES was moved to changes.go, and since
go1.19.4, comments are formatted differently.


To generate a diff of this commit:
cvs rdiff -u -r1.737 -r1.738 pkgsrc/pkgtools/pkglint/Makefile
cvs rdiff -u -r1.22 -r1.23 pkgsrc/pkgtools/pkglint/files/alternatives.go \
    pkgsrc/pkgtools/pkglint/files/mkshparser.go
cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/changes.go \
    pkgsrc/pkgtools/pkglint/files/changes_test.go
cvs rdiff -u -r1.80 -r1.81 pkgsrc/pkgtools/pkglint/files/check_test.go
cvs rdiff -u -r1.34 -r1.35 pkgsrc/pkgtools/pkglint/files/files_test.go
cvs rdiff -u -r1.6 -r1.7 pkgsrc/pkgtools/pkglint/files/homepage.go
cvs rdiff -u -r1.50 -r1.51 pkgsrc/pkgtools/pkglint/files/line.go
cvs rdiff -u -r1.28 -r1.29 pkgsrc/pkgtools/pkglint/files/logging_test.go \
    pkgsrc/pkgtools/pkglint/files/mktypes.go
cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go
cvs rdiff -u -r1.10 -r1.11 pkgsrc/pkgtools/pkglint/files/mklexer.go
cvs rdiff -u -r1.85 -r1.86 pkgsrc/pkgtools/pkglint/files/mkline.go
cvs rdiff -u -r1.84 -r1.85 pkgsrc/pkgtools/pkglint/files/mkline_test.go \
    pkgsrc/pkgtools/pkglint/files/util.go
cvs rdiff -u -r1.70 -r1.71 pkgsrc/pkgtools/pkglint/files/mklinechecker.go
cvs rdiff -u -r1.66 -r1.67 \
    pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
cvs rdiff -u -r1.45 -r1.46 pkgsrc/pkgtools/pkglint/files/mkparser.go
cvs rdiff -u -r1.13 -r1.14 pkgsrc/pkgtools/pkglint/files/mkshtypes.go
cvs rdiff -u -r1.106 -r1.107 pkgsrc/pkgtools/pkglint/files/package.go \
    pkgsrc/pkgtools/pkglint/files/vardefs.go
cvs rdiff -u -r1.92 -r1.93 pkgsrc/pkgtools/pkglint/files/package_test.go
cvs rdiff -u -r1.12 -r1.13 pkgsrc/pkgtools/pkglint/files/path.go
cvs rdiff -u -r1.86 -r1.87 pkgsrc/pkgtools/pkglint/files/pkglint.go
cvs rdiff -u -r1.72 -r1.73 pkgsrc/pkgtools/pkglint/files/pkglint_test.go
cvs rdiff -u -r1.68 -r1.69 pkgsrc/pkgtools/pkglint/files/pkgsrc.go
cvs rdiff -u -r1.56 -r1.57 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
cvs rdiff -u -r1.63 -r1.64 pkgsrc/pkgtools/pkglint/files/plist.go
cvs rdiff -u -r1.2 -r1.3 pkgsrc/pkgtools/pkglint/files/scope.go
cvs rdiff -u -r1.65 -r1.66 pkgsrc/pkgtools/pkglint/files/shell.go
cvs rdiff -u -r1.25 -r1.26 pkgsrc/pkgtools/pkglint/files/shtokenizer.go
cvs rdiff -u -r1.17 -r1.18 pkgsrc/pkgtools/pkglint/files/shtypes.go
cvs rdiff -u -r1.37 -r1.38 pkgsrc/pkgtools/pkglint/files/substcontext_test.go
cvs rdiff -u -r1.26 -r1.27 pkgsrc/pkgtools/pkglint/files/tools.go
cvs rdiff -u -r1.9 -r1.10 pkgsrc/pkgtools/pkglint/files/var.go
cvs rdiff -u -r1.20 -r1.21 pkgsrc/pkgtools/pkglint/files/varalignblock.go
cvs rdiff -u -r1.18 -r1.19 \
    pkgsrc/pkgtools/pkglint/files/varalignblock_test.go
cvs rdiff -u -r1.54 -r1.55 pkgsrc/pkgtools/pkglint/files/vartype.go
cvs rdiff -u -r1.102 -r1.103 pkgsrc/pkgtools/pkglint/files/vartypecheck.go
cvs rdiff -u -r1.94 -r1.95 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
cvs rdiff -u -r1.9 -r1.10 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go
cvs rdiff -u -r1.5 -r1.6 pkgsrc/pkgtools/pkglint/files/intqa/qa.go
cvs rdiff -u -r1.3 -r1.4 pkgsrc/pkgtools/pkglint/files/makepat/pat.go
cvs rdiff -u -r1.11 -r1.12 pkgsrc/pkgtools/pkglint/files/trace/tracing.go

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/pkgtools/pkglint/Makefile
diff -u pkgsrc/pkgtools/pkglint/Makefile:1.737 pkgsrc/pkgtools/pkglint/Makefile:1.738
--- pkgsrc/pkgtools/pkglint/Makefile:1.737      Wed Jan 11 17:28:33 2023
+++ pkgsrc/pkgtools/pkglint/Makefile    Sun Jan 29 13:36:31 2023
@@ -1,7 +1,6 @@
-# $NetBSD: Makefile,v 1.737 2023/01/11 17:28:33 bsiegert Exp $
+# $NetBSD: Makefile,v 1.738 2023/01/29 13:36:31 rillig Exp $
 
-PKGNAME=       pkglint-22.3.2
-PKGREVISION=   2
+PKGNAME=       pkglint-22.4.0
 CATEGORIES=    pkgtools
 
 MAINTAINER=    rillig%NetBSD.org@localhost

Index: pkgsrc/pkgtools/pkglint/files/alternatives.go
diff -u pkgsrc/pkgtools/pkglint/files/alternatives.go:1.22 pkgsrc/pkgtools/pkglint/files/alternatives.go:1.23
--- pkgsrc/pkgtools/pkglint/files/alternatives.go:1.22  Thu Jul 28 06:37:04 2022
+++ pkgsrc/pkgtools/pkglint/files/alternatives.go       Sun Jan 29 13:36:31 2023
@@ -29,7 +29,8 @@ func (ck *AlternativesChecker) Check(lin
 }
 
 // checkLine checks a single line for the following format:
-//  wrapper alternative [arguments]
+//
+//     wrapper alternative [arguments]
 func (ck *AlternativesChecker) checkLine(line *Line, plistFiles map[RelPath]*PlistLine, pkg *Package) {
        m, wrapper, space, alternative := match3(line.Text, `^([^\t ]+)([ \t]+)([^\t ]+).*$`)
        if !m {
Index: pkgsrc/pkgtools/pkglint/files/mkshparser.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshparser.go:1.22 pkgsrc/pkgtools/pkglint/files/mkshparser.go:1.23
--- pkgsrc/pkgtools/pkglint/files/mkshparser.go:1.22    Mon Nov 28 23:33:28 2022
+++ pkgsrc/pkgtools/pkglint/files/mkshparser.go Sun Jan 29 13:36:31 2023
@@ -37,19 +37,22 @@ func (e *ParseError) Error() string {
 // The main work of tokenizing is done in ShellTokenizer though.
 //
 // Example:
-//  while :; do var=$$other; done
+//
+//     while :; do var=$$other; done
+//
 // =>
-//  while
-//  space " "
-//  word ":"
-//  semicolon
-//  space " "
-//  do
-//  space " "
-//  assign "var=$$other"
-//  semicolon
-//  space " "
-//  done
+//
+//     while
+//     space " "
+//     word ":"
+//     semicolon
+//     space " "
+//     do
+//     space " "
+//     assign "var=$$other"
+//     semicolon
+//     space " "
+//     done
 //
 // See splitIntoShellTokens and ShellTokenizer.
 type ShellLexer struct {

Index: pkgsrc/pkgtools/pkglint/files/check_test.go
diff -u pkgsrc/pkgtools/pkglint/files/check_test.go:1.80 pkgsrc/pkgtools/pkglint/files/check_test.go:1.81
--- pkgsrc/pkgtools/pkglint/files/check_test.go:1.80    Fri Jun 24 07:16:23 2022
+++ pkgsrc/pkgtools/pkglint/files/check_test.go Sun Jan 29 13:36:31 2023
@@ -101,7 +101,7 @@ func (s *Suite) TearDownTest(c *check.C)
 
 // Ensures that all test names follow a common naming scheme:
 //
-//  Test_${Type}_${Method}__${description_using_underscores}
+//     Test_${Type}_${Method}__${description_using_underscores}
 func Test__qa(t *testing.T) {
        ck := intqa.NewQAChecker(t.Errorf)
 
@@ -259,8 +259,9 @@ func (t *Tester) SetUpTool(name, varname
 // in the variable definitions in vardefs.go.
 //
 // Example:
-//  SetUpVarType("PKGPATH", BtPkgpath, DefinedIfInScope|NonemptyIfDefined,
-//      "Makefile, *.mk: default, set, append, use, use-loadtime")
+//
+//     SetUpVarType("PKGPATH", BtPkgpath, DefinedIfInScope|NonemptyIfDefined,
+//         "Makefile, *.mk: default, set, append, use, use-loadtime")
 func (t *Tester) SetUpVarType(varname string, basicType *BasicType,
        options vartypeOptions, aclEntries ...string) {
 
@@ -713,20 +714,20 @@ func (t *Tester) Remove(filename RelPath
 // that include each other.
 // The hierarchy is created only in memory, nothing is written to disk.
 //
-//  include, get := t.SetUpHierarchy()
+//     include, get := t.SetUpHierarchy()
 //
-//  include("including.mk",
-//      include("other.mk",
-//          "VAR= other"),
-//      include("subdir/module.mk",
-//          "VAR= module",
-//          include("subdir/version.mk",
-//              "VAR= version"),
-//          include("subdir/env.mk",
-//              "VAR= env")))
+//     include("including.mk",
+//         include("other.mk",
+//             "VAR= other"),
+//         include("subdir/module.mk",
+//             "VAR= module",
+//             include("subdir/version.mk",
+//                 "VAR= version"),
+//             include("subdir/env.mk",
+//                 "VAR= env")))
 //
-//  mklines := get("including.mk")
-//  module := get("subdir/module.mk")
+//     mklines := get("including.mk")
+//     module := get("subdir/module.mk")
 //
 // The filenames passed to the include function are all relative to the
 // same location, which is typically the pkgsrc root or a package directory,
@@ -735,7 +736,8 @@ func (t *Tester) Remove(filename RelPath
 // The generated .include lines take the relative paths into account.
 // For example, when subdir/module.mk includes subdir/version.mk,
 // the include line is just:
-//  .include "version.mk"
+//
+//     .include "version.mk"
 func (t *Tester) SetUpHierarchy() (
        include func(filename RelPath, args ...interface{}) *MkLines,
        get func(path RelPath) *MkLines) {
@@ -942,9 +944,10 @@ func (t *Tester) InternalErrorf(format s
 // Line.Fatalf or uses some other way to panic with a pkglintFatal.
 //
 // Usage:
-//  t.ExpectFatal(
-//      func() { /* do something that panics */ },
-//      "FATAL: ~/Makefile:1: Must not be empty")
+//
+//     t.ExpectFatal(
+//         func() { /* do something that panics */ },
+//         "FATAL: ~/Makefile:1: Must not be empty")
 func (t *Tester) ExpectFatal(action func(), expectedLines ...string) {
        defer func() {
                r := recover()
@@ -965,9 +968,10 @@ func (t *Tester) ExpectFatal(action func
 // It then matches the output against the given regular expression.
 //
 // Usage:
-//  t.ExpectFatalMatches(
-//      func() { /* do something that panics */ },
-//      `FATAL: ~/Makefile:1: .*\n`)
+//
+//     t.ExpectFatalMatches(
+//         func() { /* do something that panics */ },
+//         `FATAL: ~/Makefile:1: .*\n`)
 func (t *Tester) ExpectFatalMatches(action func(), expected regex.Pattern) {
        defer func() {
                r := recover()
@@ -988,9 +992,10 @@ func (t *Tester) ExpectFatalMatches(acti
 // assert or assertf, or uses some other way to panic.
 //
 // Usage:
-//  t.ExpectPanic(
-//      func() { /* do something that panics */ },
-//      "runtime error: path not found")
+//
+//     t.ExpectPanic(
+//         func() { /* do something that panics */ },
+//         "runtime error: path not found")
 func (t *Tester) ExpectPanic(action func(), expectedMessage string) {
        t.Check(action, check.Panics, expectedMessage)
 }
@@ -1005,8 +1010,9 @@ func (t *Tester) ExpectPanicMatches(acti
 // ExpectAssert runs the given action and expects that this action calls assert.
 //
 // Usage:
-//  t.ExpectAssert(
-//      func() { /* do something that panics */ })
+//
+//     t.ExpectAssert(
+//         func() { /* do something that panics */ })
 func (t *Tester) ExpectAssert(action func()) {
        t.Check(action, check.Panics, "Pkglint internal error")
 }

Index: pkgsrc/pkgtools/pkglint/files/files_test.go
diff -u pkgsrc/pkgtools/pkglint/files/files_test.go:1.34 pkgsrc/pkgtools/pkglint/files/files_test.go:1.35
--- pkgsrc/pkgtools/pkglint/files/files_test.go:1.34    Sat Jan  1 12:44:24 2022
+++ pkgsrc/pkgtools/pkglint/files/files_test.go Sun Jan 29 13:36:31 2023
@@ -115,7 +115,9 @@ func (s *Suite) Test_convertToLogicalLin
 // This test demonstrates that pkglint deviates from bmake.
 // Bmake keeps all the trailing whitespace from the line and replaces the
 // backslash plus any indentation with a single space. This results in:
-//  "\tprintf '%s\\n' 'none none  space  space  tab\t tab'"
+//
+//     "\tprintf '%s\\n' 'none none  space  space  tab\t tab'"
+//
 // This is another of the edge cases probably no-one relies on.
 func (s *Suite) Test_convertToLogicalLines__continuation_spacing(c *check.C) {
        t := s.Init(c)

Index: pkgsrc/pkgtools/pkglint/files/homepage.go
diff -u pkgsrc/pkgtools/pkglint/files/homepage.go:1.6 pkgsrc/pkgtools/pkglint/files/homepage.go:1.7
--- pkgsrc/pkgtools/pkglint/files/homepage.go:1.6       Thu Oct 28 20:15:25 2021
+++ pkgsrc/pkgtools/pkglint/files/homepage.go   Sun Jan 29 13:36:31 2023
@@ -17,24 +17,25 @@ import (
 // from less preferred URLs to preferred URLs.
 //
 // For most sites, the list of possible URLs is:
-//  - https://$rest (preferred)
-//  - http://$rest (less preferred)
+//   - https://$rest (preferred)
+//   - http://$rest (less preferred)
 //
 // For SourceForge, it's a little more complicated:
-//  - https://$project.sourceforge.io/$path
-//  - http://$project.sourceforge.net/$path
-//  - http://$project.sourceforge.io/$path (not officially supported)
-//  - https://$project.sourceforge.net/$path (not officially supported)
-//  - https://sourceforge.net/projects/$project/
-//  - http://sourceforge.net/projects/$project/
-//  - https://sf.net/projects/$project/
-//  - http://sf.net/projects/$project/
-//  - https://sf.net/p/$project/
-//  - http://sf.net/p/$project/
+//   - https://$project.sourceforge.io/$path
+//   - http://$project.sourceforge.net/$path
+//   - http://$project.sourceforge.io/$path (not officially supported)
+//   - https://$project.sourceforge.net/$path (not officially supported)
+//   - https://sourceforge.net/projects/$project/
+//   - http://sourceforge.net/projects/$project/
+//   - https://sf.net/projects/$project/
+//   - http://sf.net/projects/$project/
+//   - https://sf.net/p/$project/
+//   - http://sf.net/p/$project/
 //
 // TODO: implement complete homepage migration for SourceForge.
 // TODO: allow to suppress the automatic migration for SourceForge,
-//  even if it is not about https vs. http.
+//
+//     even if it is not about https vs. http.
 type HomepageChecker struct {
        Value      string
        ValueNoVar string

Index: pkgsrc/pkgtools/pkglint/files/line.go
diff -u pkgsrc/pkgtools/pkglint/files/line.go:1.50 pkgsrc/pkgtools/pkglint/files/line.go:1.51
--- pkgsrc/pkgtools/pkglint/files/line.go:1.50  Sat Nov 13 21:20:59 2021
+++ pkgsrc/pkgtools/pkglint/files/line.go       Sun Jan 29 13:36:31 2023
@@ -177,25 +177,25 @@ func (line *Line) String() string {
 //
 // Usage:
 //
-//  fix := line.Autofix()
+//     fix := line.Autofix()
 //
-//  fix.Errorf("Must not be ...")
-//  fix.Warnf("Should not be ...")
-//  fix.Notef("It is also possible ...")
-//  fix.Silent()
+//     fix.Errorf("Must not be ...")
+//     fix.Warnf("Should not be ...")
+//     fix.Notef("It is also possible ...")
+//     fix.Silent()
 //
-//  fix.Explain(
-//      "Explanation ...",
-//      "... end of explanation.")
+//     fix.Explain(
+//         "Explanation ...",
+//         "... end of explanation.")
 //
-//  fix.Replace("from", "to")
-//  fix.ReplaceAfter("prefix", "from", "to")
-//  fix.InsertAbove("new line")
-//  fix.InsertBelow("new line")
-//  fix.Delete()
-//  fix.Custom(func(showAutofix, autofix bool) {})
+//     fix.Replace("from", "to")
+//     fix.ReplaceAfter("prefix", "from", "to")
+//     fix.InsertAbove("new line")
+//     fix.InsertBelow("new line")
+//     fix.Delete()
+//     fix.Custom(func(showAutofix, autofix bool) {})
 //
-//  fix.Apply()
+//     fix.Apply()
 func (line *Line) Autofix() *Autofix {
        if line.fix == nil {
                line.fix = NewAutofix(line)

Index: pkgsrc/pkgtools/pkglint/files/logging_test.go
diff -u pkgsrc/pkgtools/pkglint/files/logging_test.go:1.28 pkgsrc/pkgtools/pkglint/files/logging_test.go:1.29
--- pkgsrc/pkgtools/pkglint/files/logging_test.go:1.28  Fri Jun 25 14:15:01 2021
+++ pkgsrc/pkgtools/pkglint/files/logging_test.go       Sun Jan 29 13:36:31 2023
@@ -597,7 +597,8 @@ func (s *Suite) Test_Logger_writeSource_
 // output lines.
 //
 // TODO: Giving the diagnostics again would be useful, but the warning and
-//  error counters should not be affected, as well as the exitcode.
+//
+//     error counters should not be affected, as well as the exitcode.
 func (s *Suite) Test_Logger_writeSource__separator_autofix(c *check.C) {
        t := s.Init(c)
 
Index: pkgsrc/pkgtools/pkglint/files/mktypes.go
diff -u pkgsrc/pkgtools/pkglint/files/mktypes.go:1.28 pkgsrc/pkgtools/pkglint/files/mktypes.go:1.29
--- pkgsrc/pkgtools/pkglint/files/mktypes.go:1.28       Sat Nov 19 10:51:07 2022
+++ pkgsrc/pkgtools/pkglint/files/mktypes.go    Sun Jan 29 13:36:31 2023
@@ -13,7 +13,6 @@ import (
 //  1. MkToken{Text: "/usr/share/"}
 //  2. MkToken{Text: "${PKGNAME}", Varuse: NewMkVarUse("PKGNAME")}
 //  3. MkToken{Text: "/data"}
-//
 type MkToken struct {
        Text   string    // Used for both literal text and variable uses
        Varuse *MkVarUse // For literal text, it is nil
@@ -77,7 +76,8 @@ func (m MkVarUseModifier) MatchSubst() (
 // Subst evaluates an S/from/to/ modifier.
 //
 // Example:
-//  MkVarUseModifier{"S,name,file,g"}.Subst("distname-1.0") => "distfile-1.0"
+//
+//     MkVarUseModifier{"S,name,file,g"}.Subst("distname-1.0") => "distfile-1.0"
 func (m MkVarUseModifier) Subst(str string) (bool, string) {
        // XXX: The call to MatchSubst is usually redundant because MatchSubst
        //  is typically called directly before calling Subst.
@@ -137,13 +137,14 @@ func (MkVarUseModifier) EvalSubst(s stri
 
 // MatchMatch tries to match the modifier to a :M or a :N pattern matching.
 // Examples:
-//  modifier    =>   ok     positive  pattern    exact
-//  --------------------------------------------------
-//  :Mpattern   =>   true   true      "pattern"  true
-//  :M*         =>   true   true      "*"        false
-//  :M${VAR}    =>   true   true      "${VAR}"   false
-//  :Npattern   =>   true   false     "pattern"  true
-//  :X          =>   false
+//
+//     modifier    =>   ok     positive  pattern    exact
+//     --------------------------------------------------
+//     :Mpattern   =>   true   true      "pattern"  true
+//     :M*         =>   true   true      "*"        false
+//     :M${VAR}    =>   true   true      "${VAR}"   false
+//     :Npattern   =>   true   false     "pattern"  true
+//     :X          =>   false
 func (m MkVarUseModifier) MatchMatch() (ok bool, positive bool, pattern string, exact bool) {
        if m.HasPrefix("M") || m.HasPrefix("N") {
                str := m.String()

Index: pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go
diff -u pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go:1.3 pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go:1.4
--- pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go:1.3       Sat Nov 19 10:51:07 2022
+++ pkgsrc/pkgtools/pkglint/files/mkcondsimplifier.go   Sun Jan 29 13:36:31 2023
@@ -214,8 +214,9 @@ func (s *MkCondSimplifier) simplifyYesNo
 }
 
 // simplifyMatch replaces:
-//  !empty(VAR:M*.c) with ${VAR:M*.c}
-//  empty(VAR:M*.c) with !${VAR:M*.c}
+//
+//     !empty(VAR:M*.c) with ${VAR:M*.c}
+//     empty(VAR:M*.c) with !${VAR:M*.c}
 //
 // * fromEmpty is true for the form empty(VAR...), and false for ${VAR...}.
 //

Index: pkgsrc/pkgtools/pkglint/files/mklexer.go
diff -u pkgsrc/pkgtools/pkglint/files/mklexer.go:1.10 pkgsrc/pkgtools/pkglint/files/mklexer.go:1.11
--- pkgsrc/pkgtools/pkglint/files/mklexer.go:1.10       Sun Aug  2 13:27:17 2020
+++ pkgsrc/pkgtools/pkglint/files/mklexer.go    Sun Jan 29 13:36:31 2023
@@ -27,13 +27,16 @@ func NewMkLexer(text string, diag Autofi
 }
 
 // MkTokens splits a text like in the following example:
-//  Text${VAR:Mmodifier}${VAR2}more text${VAR3}
+//
+//     Text${VAR:Mmodifier}${VAR2}more text${VAR3}
+//
 // into tokens like these:
-//  Text
-//  ${VAR:Mmodifier}
-//  ${VAR2}
-//  more text
-//  ${VAR3}
+//
+//     Text
+//     ${VAR:Mmodifier}
+//     ${VAR2}
+//     more text
+//     ${VAR3}
 func (p *MkLexer) MkTokens() ([]*MkToken, string) {
        lexer := p.lexer
 
@@ -105,11 +108,12 @@ func (p *MkLexer) VarUse() *MkVarUse {
 }
 
 // varUseBrace parses:
-//  ${VAR}
-//  ${arbitrary text:L}
-//  ${variable with invalid chars}
-//  $(PARENTHESES)
-//  ${VAR:Mpattern:C,:,colon,g:Q:Q:Q}
+//
+//     ${VAR}
+//     ${arbitrary text:L}
+//     ${variable with invalid chars}
+//     $(PARENTHESES)
+//     ${VAR:Mpattern:C,:,colon,g:Q:Q:Q}
 func (p *MkLexer) varUseBrace(usingRoundParen bool) *MkVarUse {
        lexer := p.lexer
 

Index: pkgsrc/pkgtools/pkglint/files/mkline.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline.go:1.85 pkgsrc/pkgtools/pkglint/files/mkline.go:1.86
--- pkgsrc/pkgtools/pkglint/files/mkline.go:1.85        Fri Jun 24 07:16:23 2022
+++ pkgsrc/pkgtools/pkglint/files/mkline.go     Sun Jan 29 13:36:31 2023
@@ -121,7 +121,8 @@ func (mkline *MkLine) HasRationale(keywo
 // '#' is passed uninterpreted to the shell.
 //
 // Example:
-//  VAR=value # comment
+//
+//     VAR=value # comment
 //
 // In the above line, the comment is " comment", including the leading space.
 func (mkline *MkLine) Comment() string { return mkline.splitResult.comment }
@@ -144,9 +145,12 @@ func (mkline *MkLine) IsVarassign() bool
 // space between the # and the variable name.
 //
 // Example:
-//  #VAR=   value
+//
+//     #VAR=   value
+//
 // Counterexample:
-//  # VAR=  value
+//
+//     # VAR=  value
 func (mkline *MkLine) IsCommentedVarassign() bool {
        data, ok := mkline.data.(*mkLineAssign)
        return ok && data.commented
@@ -158,9 +162,12 @@ func (mkline *MkLine) IsCommentedVarassi
 // space between the # and the variable name.
 //
 // Example:
-//  #VAR=   value
+//
+//     #VAR=   value
+//
 // Counterexample:
-//  # VAR=  value
+//
+//     # VAR=  value
 func (mkline *MkLine) IsVarassignMaybeCommented() bool {
        _, ok := mkline.data.(*mkLineAssign)
        return ok
@@ -170,8 +177,9 @@ func (mkline *MkLine) IsVarassignMaybeCo
 // target.
 //
 // Example:
-//  pre-configure:    # IsDependency
-//          ${ECHO}   # IsShellCommand
+//
+//     pre-configure:    # IsDependency
+//             ${ECHO}   # IsShellCommand
 func (mkline *MkLine) IsShellCommand() bool {
        _, ok := mkline.data.(mkLineShell)
        return ok
@@ -222,21 +230,24 @@ func (mkline *MkLine) IsDependency() boo
 // of the variable that is assigned or appended to.
 //
 // Example:
-//  VARNAME.${param}?=      value   # Varname is "VARNAME.${param}"
+//
+//     VARNAME.${param}?=      value   # Varname is "VARNAME.${param}"
 func (mkline *MkLine) Varname() string { return mkline.data.(*mkLineAssign).varname }
 
 // Varcanon applies to variable assignments and returns the canonicalized variable name for parameterized variables.
 // Examples:
-//  HOMEPAGE           => "HOMEPAGE"
-//  SUBST_SED.anything => "SUBST_SED.*"
-//  SUBST_SED.${param} => "SUBST_SED.*"
+//
+//     HOMEPAGE           => "HOMEPAGE"
+//     SUBST_SED.anything => "SUBST_SED.*"
+//     SUBST_SED.${param} => "SUBST_SED.*"
 func (mkline *MkLine) Varcanon() string { return mkline.data.(*mkLineAssign).varcanon }
 
 // Varparam applies to variable assignments and returns the parameter for parameterized variables.
 // Examples:
-//  HOMEPAGE           => ""
-//  SUBST_SED.anything => "anything"
-//  SUBST_SED.${param} => "${param}"
+//
+//     HOMEPAGE           => ""
+//     SUBST_SED.anything => "anything"
+//     SUBST_SED.${param} => "${param}"
 func (mkline *MkLine) Varparam() string { return mkline.data.(*mkLineAssign).varparam }
 
 // Op applies to variable assignments and returns the assignment operator.
@@ -254,10 +265,10 @@ func (mkline *MkLine) Value() string { r
 // FirstLineContainsValue returns whether the variable assignment of a
 // multiline contains a textual value in the first line.
 //
-//  VALUE_IN_FIRST_LINE= value \
-//          starts in first line
-//  NO_VALUE_IN_FIRST_LINE= \
-//          value starts in second line
+//     VALUE_IN_FIRST_LINE= value \
+//             starts in first line
+//     NO_VALUE_IN_FIRST_LINE= \
+//             value starts in second line
 func (mkline *MkLine) FirstLineContainsValue() bool {
        assert(mkline.IsVarassignMaybeCommented())
        assert(mkline.IsMultiline())
@@ -275,7 +286,8 @@ func (mkline *MkLine) ShellCommand() str
 // Indent returns the whitespace between the dot and the directive.
 //
 // For the following example line it returns two spaces:
-//  .  include "other.mk"
+//
+//     .  include "other.mk"
 func (mkline *MkLine) Indent() string {
        if mkline.IsDirective() {
                return mkline.data.(*mkLineDirective).indent
@@ -362,8 +374,9 @@ func (mkline *MkLine) SetConditionalVars
 // and may thus still appear in the text. Therefore, # marks a shell comment.
 //
 // Example:
-//  input:  ${PREFIX}/bin abc
-//  output: [MkToken("${PREFIX}", MkVarUse("PREFIX")), MkToken("/bin abc")]
+//
+//     input:  ${PREFIX}/bin abc
+//     output: [MkToken("${PREFIX}", MkVarUse("PREFIX")), MkToken("/bin abc")]
 //
 // See ValueTokens, which is the tokenized version of Value.
 func (mkline *MkLine) Tokenize(text string, warn bool) []*MkToken {
@@ -393,11 +406,11 @@ func (mkline *MkLine) Tokenize(text stri
 // ValueSplit splits the given value, taking care of variable references.
 // Example:
 //
-//  ValueSplit("${VAR:Udefault}::${VAR2}two:words", ":")
-//  => "${VAR:Udefault}"
-//     ""
-//     "${VAR2}two"
-//     "words"
+//     ValueSplit("${VAR:Udefault}::${VAR2}two:words", ":")
+//     => "${VAR:Udefault}"
+//        ""
+//        "${VAR2}two"
+//        "words"
 //
 // Note that even though the first word contains a colon, it is not split
 // at that point since the colon is inside a variable use.
@@ -448,11 +461,11 @@ var notSpace = textproc.Space.Inverse()
 // ValueFields splits the given value in the same way as the :M variable
 // modifier, taking care of variable references. Example:
 //
-//  ValueFields("${VAR:Udefault value} ${VAR2}two words;;; 'word three'")
-//  => "${VAR:Udefault value}"
-//     "${VAR2}two"
-//     "words;;;"
-//     "'word three'"
+//     ValueFields("${VAR:Udefault value} ${VAR2}two words;;; 'word three'")
+//     => "${VAR:Udefault value}"
+//        "${VAR2}two"
+//        "words;;;"
+//        "'word three'"
 //
 // Note that even though the first word contains a space, it is not split
 // at that point since the space is inside a variable use. Shell tokens
@@ -1071,10 +1084,10 @@ func (vuc *VarUseContext) String() strin
 // An excepting are multiple-inclusion guards, they don't increase the
 // indentation.
 //
-//  Indentation starts with 0 spaces.
-//  Each .if or .for indents all inner directives by 2.
-//  Except for .if with multiple-inclusion guard, which indents all inner directives by 0.
-//  Each .elif, .else, .endif, .endfor uses the outer indentation instead.
+//     Indentation starts with 0 spaces.
+//     Each .if or .for indents all inner directives by 2.
+//     Except for .if with multiple-inclusion guard, which indents all inner directives by 0.
+//     Each .elif, .else, .endif, .endfor uses the outer indentation instead.
 type Indentation struct {
        levels []indentationLevel
 }
@@ -1219,7 +1232,6 @@ func (ind *Indentation) AddCheckedFile(f
 
 // HasExists returns whether the given filename has been tested in an
 // exists(filename) condition and thus may or may not exist.
-//
 func (ind *Indentation) HasExists(filename PkgsrcPath) bool {
        for _, level := range ind.levels {
                for _, levelFilename := range level.checkedFiles {
@@ -1327,7 +1339,8 @@ func (ind *Indentation) CheckFinish(file
 // which would confuse the devel/bmake parser.
 //
 // TODO: The allowed characters differ between the basename and the parameter
-//  of the variable. The square bracket is only allowed in the parameter part.
+//
+//     of the variable. The square bracket is only allowed in the parameter part.
 var (
        // TODO: remove the ','
        VarbaseBytes = textproc.NewByteSet("A-Za-z_0-9-+,")

Index: pkgsrc/pkgtools/pkglint/files/mkline_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.84 pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.85
--- pkgsrc/pkgtools/pkglint/files/mkline_test.go:1.84   Sat Jan  1 12:44:24 2022
+++ pkgsrc/pkgtools/pkglint/files/mkline_test.go        Sun Jan 29 13:36:31 2023
@@ -1011,7 +1011,8 @@ func (s *Suite) Test_MkLine_VariableNeed
 }
 
 // TODO: COMPILER_RPATH_FLAG and LINKER_RPATH_FLAG have different types
-//  defined in vardefs.go; examine why.
+//
+//     defined in vardefs.go; examine why.
 func (s *Suite) Test_MkLine_VariableNeedsQuoting__shellword_part(c *check.C) {
        t := s.Init(c)
 
Index: pkgsrc/pkgtools/pkglint/files/util.go
diff -u pkgsrc/pkgtools/pkglint/files/util.go:1.84 pkgsrc/pkgtools/pkglint/files/util.go:1.85
--- pkgsrc/pkgtools/pkglint/files/util.go:1.84  Sun Oct  2 14:39:37 2022
+++ pkgsrc/pkgtools/pkglint/files/util.go       Sun Jan 29 13:36:31 2023
@@ -606,7 +606,7 @@ func (o *Once) check(key uint64) bool {
 
 // The MIT License (MIT)
 //
-// Copyright (c) 2015 Frits van Bommel
+// # Copyright (c) 2015 Frits van Bommel
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.70 pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.71
--- pkgsrc/pkgtools/pkglint/files/mklinechecker.go:1.70 Fri Jun 25 14:15:01 2021
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker.go      Sun Jan 29 13:36:31 2023
@@ -119,6 +119,7 @@ func (ck MkLineChecker) checkTextRpath(t
                return
        }
 
+       // See VartypeCheck.LdFlag.
        if m, flag := match1(text, `(-Wl,--rpath,|-Wl,-rpath-link,|-Wl,-rpath,|-Wl,-R\b)`); m {
                mkline.Warnf("Please use ${COMPILER_RPATH_FLAG} instead of %q.", flag)
        }

Index: pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go
diff -u pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.66 pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.67
--- pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go:1.66    Sat Jan  1 12:44:25 2022
+++ pkgsrc/pkgtools/pkglint/files/mklinechecker_test.go Sun Jan 29 13:36:31 2023
@@ -186,13 +186,26 @@ func (s *Suite) Test_MkLineChecker_check
                "BUILDLINK_TRANSFORM+=\t\topt:-Wl,-rpath,/usr/lib",
                "BUILDLINK_TRANSFORM.pkgbase+=\trm:-Wl,-R/usr/lib",
                "BUILDLINK_TRANSFORM.pkgbase+=\trm:-Wl,-rpath,/usr/lib",
-               "BUILDLINK_TRANSFORM.pkgbase+=\topt:-Wl,-rpath,/usr/lib")
+               "BUILDLINK_TRANSFORM.pkgbase+=\topt:-Wl,-rpath,/usr/lib",
+               "",
+               "LDFLAGS+=\t-Wl,-R${PREFIX}/gcc9/lib",
+               "LDFLAGS+=\t-Wl,-rpath,${PREFIX}/gcc9/lib",
+               "LDFLAGS+=\t-Wl,--rpath,${PREFIX}/gcc9/lib")
 
        mklines.Check()
 
        t.CheckOutputLines(
                "WARN: filename.mk:4: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,-rpath,\".",
-               "WARN: filename.mk:7: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,-rpath,\".")
+               "WARN: filename.mk:7: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,-rpath,\".",
+               // TODO: Remove duplicates.
+               "WARN: filename.mk:9: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,-R\".",
+               "WARN: filename.mk:9: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,-R\".",
+               // TODO: Remove duplicates.
+               "WARN: filename.mk:10: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,-rpath,\".",
+               "WARN: filename.mk:10: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,-rpath,\".",
+               // TODO: Remove duplicates.
+               "WARN: filename.mk:11: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,--rpath,\".",
+               "WARN: filename.mk:11: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,--rpath,\".")
 }
 
 func (s *Suite) Test_MkLineChecker_checkVartype__simple_type(c *check.C) {

Index: pkgsrc/pkgtools/pkglint/files/mkparser.go
diff -u pkgsrc/pkgtools/pkglint/files/mkparser.go:1.45 pkgsrc/pkgtools/pkglint/files/mkparser.go:1.46
--- pkgsrc/pkgtools/pkglint/files/mkparser.go:1.45      Sun Jul 24 20:07:20 2022
+++ pkgsrc/pkgtools/pkglint/files/mkparser.go   Sun Jan 29 13:36:31 2023
@@ -169,10 +169,12 @@ func (p *MkParser) mkCondCompare() *MkCo
 }
 
 // mkCondTerm parses the following:
-//  ${VAR}
-//  "${VAR}"
-//  "text${VAR}text"
-//  "text"
+//
+//     ${VAR}
+//     "${VAR}"
+//     "text${VAR}text"
+//     "text"
+//
 // It does not parse unquoted string literals since these are only allowed
 // at the right-hand side of a comparison expression.
 func (p *MkParser) mkCondTerm() *MkCondTerm {

Index: pkgsrc/pkgtools/pkglint/files/mkshtypes.go
diff -u pkgsrc/pkgtools/pkglint/files/mkshtypes.go:1.13 pkgsrc/pkgtools/pkglint/files/mkshtypes.go:1.14
--- pkgsrc/pkgtools/pkglint/files/mkshtypes.go:1.13     Fri Aug  2 18:55:07 2019
+++ pkgsrc/pkgtools/pkglint/files/mkshtypes.go  Sun Jan 29 13:36:31 2023
@@ -8,7 +8,8 @@ import (
 // MkShList is a list of shell commands, separated by newlines or semicolons.
 //
 // Example:
-//  cd $dir && echo "In $dir"; cd ..; ls -l
+//
+//     cd $dir && echo "In $dir"; cd ..; ls -l
 type MkShList struct {
        AndOrs []*MkShAndOr
 
@@ -38,7 +39,8 @@ func (list *MkShList) AddSeparator(separ
 // strictly from left to right.
 //
 // Example:
-//  cd $dir && echo "In $dir" || echo "Cannot cd into $dir"
+//
+//     cd $dir && echo "In $dir" || echo "Cannot cd into $dir"
 type MkShAndOr struct {
        Pipes []*MkShPipeline
        Ops   []string // Each element is either "&&" or "||"
@@ -74,9 +76,10 @@ func (pipe *MkShPipeline) Add(cmd *MkShC
 // MkShCommand is a simple or compound shell command.
 //
 // Examples:
-//  LC_ALL=C sort */*.c > sorted
-//  dir() { ls -l "$@"; }
-//  { echo "first"; echo "second"; }
+//
+//     LC_ALL=C sort */*.c > sorted
+//     dir() { ls -l "$@"; }
+//     { echo "first"; echo "second"; }
 type MkShCommand struct {
        Simple    *MkShSimpleCommand
        Compound  *MkShCompoundCommand
@@ -87,10 +90,11 @@ type MkShCommand struct {
 // MkShCompoundCommand is a group of commands.
 //
 // Examples:
-//  { echo "first"; echo "second"; }
-//  for f in *.c; do compile "$f"; done
-//  if [ -f "$file" ]; then echo "It exists"; fi
-//  while sleep 1; do printf .; done
+//
+//     { echo "first"; echo "second"; }
+//     for f in *.c; do compile "$f"; done
+//     if [ -f "$file" ]; then echo "It exists"; fi
+//     while sleep 1; do printf .; done
 type MkShCompoundCommand struct {
        Brace    *MkShList
        Subshell *MkShList
@@ -103,7 +107,8 @@ type MkShCompoundCommand struct {
 // MkShFor is a "for" loop.
 //
 // Example:
-//  for f in *.c; do compile "$f"; done
+//
+//     for f in *.c; do compile "$f"; done
 type MkShFor struct {
        Varname string
        Values  []*ShToken
@@ -113,7 +118,8 @@ type MkShFor struct {
 // MkShCase is a "case" statement, including all its branches.
 //
 // Example:
-//  case $filename in *.c) echo "C source" ;; esac
+//
+//     case $filename in *.c) echo "C source" ;; esac
 type MkShCase struct {
        Word  *ShToken
        Cases []*MkShCaseItem
@@ -122,7 +128,8 @@ type MkShCase struct {
 // MkShCaseItem is one branch of a "case" statement.
 //
 // Example:
-//  *.c) echo "C source" ;;
+//
+//     *.c) echo "C source" ;;
 type MkShCaseItem struct {
        Patterns  []*ShToken
        Action    *MkShList
@@ -134,7 +141,8 @@ type MkShCaseItem struct {
 // many branches.
 //
 // Example:
-//  if [ -f "$file" ]; then echo "It exists"; fi
+//
+//     if [ -f "$file" ]; then echo "It exists"; fi
 type MkShIf struct {
        Conds   []*MkShList
        Actions []*MkShList
@@ -149,7 +157,8 @@ func (cl *MkShIf) Prepend(cond *MkShList
 // MkShLoop is a "while" or "until" loop.
 //
 // Example:
-//  while sleep 1; do printf .; done
+//
+//     while sleep 1; do printf .; done
 type MkShLoop struct {
        Cond   *MkShList
        Action *MkShList
@@ -159,7 +168,8 @@ type MkShLoop struct {
 // MkShFunctionDefinition is the definition of a shell function.
 //
 // Example:
-//  dir() { ls -l "$@"; }
+//
+//     dir() { ls -l "$@"; }
 type MkShFunctionDefinition struct {
        Name string
        Body *MkShCompoundCommand
@@ -169,7 +179,8 @@ type MkShFunctionDefinition struct {
 // pipeline or conditionals.
 //
 // Example:
-//  LC_ALL=C sort */*.c > sorted
+//
+//     LC_ALL=C sort */*.c > sorted
 type MkShSimpleCommand struct {
        Assignments  []*ShToken
        Name         *ShToken
@@ -182,7 +193,8 @@ type MkShSimpleCommand struct {
 // especially for analyzing command line options.
 //
 // Example:
-//  LC_ALL=C sort */*.c > sorted
+//
+//     LC_ALL=C sort */*.c > sorted
 type StrCommand struct {
        Assignments []string
        Name        string
@@ -237,8 +249,9 @@ func (c *StrCommand) String() string {
 // MkShRedirection is a single file descriptor redirection.
 //
 // Examples:
-//  > sorted
-//  2>&1
+//
+//     > sorted
+//     2>&1
 type MkShRedirection struct {
        Fd     int      // Or -1
        Op     string   // See io_file in shell.y for possible values

Index: pkgsrc/pkgtools/pkglint/files/package.go
diff -u pkgsrc/pkgtools/pkglint/files/package.go:1.106 pkgsrc/pkgtools/pkglint/files/package.go:1.107
--- pkgsrc/pkgtools/pkglint/files/package.go:1.106      Thu Jul 28 06:37:04 2022
+++ pkgsrc/pkgtools/pkglint/files/package.go    Sun Jan 29 13:36:31 2023
@@ -1408,7 +1408,7 @@ func (pkg *Package) checkPossibleDowngra
 
        mkline := pkg.EffectivePkgnameLine
 
-       change := G.Pkgsrc.LastChange[pkg.Pkgpath]
+       change := G.Pkgsrc.changes.LastChange[pkg.Pkgpath]
        if change == nil {
                if trace.Tracing {
                        trace.Stepf("No change log for package %q", pkg.Pkgpath)
@@ -1579,8 +1579,8 @@ func (pkg *Package) checkOwnerMaintainer
 }
 
 func (pkg *Package) checkFreeze(filename CurrPath) {
-       freezeStart := G.Pkgsrc.LastFreezeStart
-       if freezeStart == "" || G.Pkgsrc.LastFreezeEnd != "" {
+       freezeStart := G.Pkgsrc.changes.LastFreezeStart
+       if freezeStart == "" || G.Pkgsrc.changes.LastFreezeEnd != "" {
                return
        }
 
@@ -1751,7 +1751,8 @@ func (pkg *Package) File(relativeFileNam
 // the package directory.
 //
 // Example:
-//  NewPackage("category/package").Rel("other/package") == "../../other/package"
+//
+//     NewPackage("category/package").Rel("other/package") == "../../other/package"
 func (pkg *Package) Rel(filename CurrPath) PackagePath {
        return NewPackagePath(G.Pkgsrc.Relpath(pkg.dir, filename))
 }
Index: pkgsrc/pkgtools/pkglint/files/vardefs.go
diff -u pkgsrc/pkgtools/pkglint/files/vardefs.go:1.106 pkgsrc/pkgtools/pkglint/files/vardefs.go:1.107
--- pkgsrc/pkgtools/pkglint/files/vardefs.go:1.106      Sun Oct  2 14:39:37 2022
+++ pkgsrc/pkgtools/pkglint/files/vardefs.go    Sun Jan 29 13:36:31 2023
@@ -87,20 +87,23 @@ func (reg *VarTypeRegistry) DefineName(v
 // individually.
 //
 // A permission entry looks like this:
-//  "Makefile, Makefile.*, *.mk: default, set, append, use, use-loadtime"
+//
+//     "Makefile, Makefile.*, *.mk: default, set, append, use, use-loadtime"
+//
 // Only certain filenames are allowed in the part before the colon,
 // to prevent typos. To use arbitrary filenames, prefix them with
 // "special:".
 //
 // Each variable that uses this function directly must document:
-//  - which of the predefined permission sets is the closest
-//  - how this individual permission set differs
-//  - why the predefined permission set is not good enough
-//  - which packages need this custom permission set.
+//   - which of the predefined permission sets is the closest
+//   - how this individual permission set differs
+//   - why the predefined permission set is not good enough
+//   - which packages need this custom permission set.
 //
 // TODO: When prefixed with "infra:", the entry should only
-//  apply to files within the pkgsrc infrastructure. Without this prefix,
-//  the pattern should only apply to files outside the pkgsrc infrastructure.
+//
+//     apply to files within the pkgsrc infrastructure. Without this prefix,
+//     the pattern should only apply to files outside the pkgsrc infrastructure.
 func (reg *VarTypeRegistry) acl(varname string, basicType *BasicType, options vartypeOptions, aclEntries ...string) {
        parsedEntries := reg.parseACLEntries(varname, aclEntries...)
        reg.Define(varname, basicType, options, parsedEntries)
@@ -110,10 +113,10 @@ func (reg *VarTypeRegistry) acl(varname 
 // the permissions individually.
 //
 // Each variable that uses this function directly must document:
-//  - which of the predefined permission sets is the closest
-//  - how this individual permission set differs
-//  - why the predefined permission set is not good enough
-//  - which packages need this custom permission set.
+//   - which of the predefined permission sets is the closest
+//   - how this individual permission set differs
+//   - why the predefined permission set is not good enough
+//   - which packages need this custom permission set.
 func (reg *VarTypeRegistry) acllist(varname string, basicType *BasicType, options vartypeOptions, aclEntries ...string) {
        reg.acl(varname, basicType, options|List, aclEntries...)
 }
@@ -206,7 +209,8 @@ func (reg *VarTypeRegistry) pkglistbl3ra
 // when these files are included.
 //
 // TODO: These timing issues should be handled separately from the permissions.
-//  They can be made more precise.
+//
+//     They can be made more precise.
 func (reg *VarTypeRegistry) sys(varname string, basicType *BasicType, options ...vartypeOptions) {
        reg.DefineName(varname, basicType, reg.options(SystemProvided, options), "sys")
 }

Index: pkgsrc/pkgtools/pkglint/files/package_test.go
diff -u pkgsrc/pkgtools/pkglint/files/package_test.go:1.92 pkgsrc/pkgtools/pkglint/files/package_test.go:1.93
--- pkgsrc/pkgtools/pkglint/files/package_test.go:1.92  Thu Jul 28 06:37:04 2022
+++ pkgsrc/pkgtools/pkglint/files/package_test.go       Sun Jan 29 13:36:31 2023
@@ -329,12 +329,13 @@ func (s *Suite) Test_Package__case_insen
 }
 
 // This package has several identifiers that all differ:
-//  - it lives in the directory "package"
-//  - the package name is "pkgname"
-//  - it downloads "distname-1.0.tar.gz"
-//    (in some places the distname is used as the package name)
-//  - in options.mk its name is "optid"
-//  - in buildlink3.mk its name is "bl3id"
+//   - it lives in the directory "package"
+//   - the package name is "pkgname"
+//   - it downloads "distname-1.0.tar.gz"
+//     (in some places the distname is used as the package name)
+//   - in options.mk its name is "optid"
+//   - in buildlink3.mk its name is "bl3id"
+//
 // All these identifiers should ideally be the same.
 // For historic reasons, the package directory and the package name
 // may differ.
@@ -3152,7 +3153,7 @@ func (s *Suite) Test_Package_checkPossib
 
        t.CreateFileLines("doc/CHANGES-2018",
                "\tUpdated category/pkgbase to 1.8 [committer 2018-01-05]")
-       G.Pkgsrc.loadDocChanges()
+       G.Pkgsrc.changes.load(&G.Pkgsrc)
 
        t.Chdir("category/pkgbase")
        pkg := NewPackage(".")
@@ -3164,7 +3165,7 @@ func (s *Suite) Test_Package_checkPossib
        t.CheckOutputLines(
                "WARN: Makefile:5: The package is being downgraded from 1.8 (see ../../doc/CHANGES-2018:1) to 1.0nb15.")
 
-       G.Pkgsrc.LastChange["category/pkgbase"].target = "1.0nb22"
+       G.Pkgsrc.changes.LastChange["category/pkgbase"].target = "1.0nb22"
 
        pkg.checkPossibleDowngrade()
 
@@ -3186,7 +3187,7 @@ func (s *Suite) Test_Package_checkPossib
        pkg.determineEffectivePkgVars()
        pkg.checkPossibleDowngrade()
 
-       t.CheckEquals(G.Pkgsrc.LastChange["category/pkgbase"].Action, Moved)
+       t.CheckEquals(G.Pkgsrc.changes.LastChange["category/pkgbase"].Action, Moved)
        // No warning because the latest action is not Updated.
        t.CheckOutputEmpty()
 }

Index: pkgsrc/pkgtools/pkglint/files/path.go
diff -u pkgsrc/pkgtools/pkglint/files/path.go:1.12 pkgsrc/pkgtools/pkglint/files/path.go:1.13
--- pkgsrc/pkgtools/pkglint/files/path.go:1.12  Sat Apr 17 18:10:14 2021
+++ pkgsrc/pkgtools/pkglint/files/path.go       Sun Jan 29 13:36:31 2023
@@ -413,8 +413,8 @@ func (p PkgsrcPath) JoinNoClean(other Re
 // conflicts on other packages.
 //
 // It can have two forms:
-//  - patches (further down)
-//  - ../../category/package/* (up to the pkgsrc root, then down again)
+//   - patches (further down)
+//   - ../../category/package/* (up to the pkgsrc root, then down again)
 type PackagePath string
 
 func NewPackagePath(p RelPath) PackagePath {

Index: pkgsrc/pkgtools/pkglint/files/pkglint.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint.go:1.86 pkgsrc/pkgtools/pkglint/files/pkglint.go:1.87
--- pkgsrc/pkgtools/pkglint/files/pkglint.go:1.86       Wed Aug 17 20:41:51 2022
+++ pkgsrc/pkgtools/pkglint/files/pkglint.go    Sun Jan 29 13:36:31 2023
@@ -671,7 +671,7 @@ func (p *Pkglint) checkReg(filename Curr
 
        case basename.HasPrefixText("CHANGES-"):
                // This only checks the file but doesn't register the changes globally.
-               _ = p.Pkgsrc.loadDocChangesFromFile(filename, true)
+               _ = (&Changes{}).parseFile(filename, true)
 
        case filename.Dir().HasBase("files"):
                // Skip files directly in the files/ directory, but not those further down.

Index: pkgsrc/pkgtools/pkglint/files/pkglint_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.72 pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.73
--- pkgsrc/pkgtools/pkglint/files/pkglint_test.go:1.72  Sat Jan  1 12:44:25 2022
+++ pkgsrc/pkgtools/pkglint/files/pkglint_test.go       Sun Jan 29 13:36:31 2023
@@ -267,26 +267,26 @@ func (s *Suite) Test_Pkglint_Main__autof
 
 // Run pkglint in a realistic environment.
 //
-//  env \
-//  PKGLINT_TESTDIR="..." \
-//  PKGLINT_TESTCMDLINE="-r" \
-//  go test -covermode=count -test.coverprofile pkglint.cov
+//     env \
+//     PKGLINT_TESTDIR="..." \
+//     PKGLINT_TESTCMDLINE="-r" \
+//     go test -covermode=count -test.coverprofile pkglint.cov
 //
-//  go tool cover -html=pkglint.cov -o coverage.html
+//     go tool cover -html=pkglint.cov -o coverage.html
 //
 // To measure the branch coverage of pkglint checking a complete pkgsrc installation,
 // install https://github.com/rillig/gobco and adjust the following code:
 //
-//  env \
-//      PKGLINT_TESTDIR="C:/Users/rillig/git/pkgsrc" \
-//      PKGLINT_TESTCMDLINE="-r -Wall -Call -p -s -e" \
-//  gobco \
-//      -test=-test.covermode=count
-//      -test=-test.coverprofile="C:/Users/rillig/go/src/netbsd.org/pkglint/stats-go.txt"
-//      -test=-timeout=3600s \
-//      -test=-check.f="^Test_Pkglint_Main__realistic" \
-//      -stats="stats-gobco.json" \
-//      > out
+//     env \
+//         PKGLINT_TESTDIR="C:/Users/rillig/git/pkgsrc" \
+//         PKGLINT_TESTCMDLINE="-r -Wall -Call -p -s -e" \
+//     gobco \
+//         -test=-test.covermode=count
+//         -test=-test.coverprofile="C:/Users/rillig/go/src/netbsd.org/pkglint/stats-go.txt"
+//         -test=-timeout=3600s \
+//         -test=-check.f="^Test_Pkglint_Main__realistic" \
+//         -stats="stats-gobco.json" \
+//         > out
 //
 // Note that the path to -test.coverprofile must be absolute, since gobco
 // runs "go test" in a temporary directory.

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.68 pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.69
--- pkgsrc/pkgtools/pkglint/files/pkgsrc.go:1.68        Sat Nov 19 10:51:07 2022
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc.go     Sun Jan 29 13:36:31 2023
@@ -1,9 +1,7 @@
 package pkglint
 
 import (
-       "netbsd.org/pkglint/pkgver"
        "netbsd.org/pkglint/regex"
-       "netbsd.org/pkglint/textproc"
        "os"
        "path/filepath"
        "sort"
@@ -35,10 +33,7 @@ type Pkgsrc struct {
        suggestedUpdates    []SuggestedUpdate
        suggestedWipUpdates []SuggestedUpdate
 
-       LastChange      map[PkgsrcPath]*Change
-       LastFreezeStart string // e.g. "2018-01-01", or ""
-       LastFreezeEnd   string // e.g. "2018-01-01", or ""
-
+       changes      Changes
        listVersions map[string][]string // See Pkgsrc.ListVersions
 
        // Variables that may be overridden by the pkgsrc user.
@@ -62,9 +57,7 @@ func NewPkgsrc(dir CurrPath) Pkgsrc {
                make(map[string]string),
                nil,
                nil,
-               make(map[PkgsrcPath]*Change),
-               "",
-               "",
+               Changes{},
                make(map[string][]string),
                NewScope(),
                make(map[string]string),
@@ -82,7 +75,7 @@ func (src *Pkgsrc) LoadInfrastructure() 
        src.vartypes.Init(src)
        src.loadMasterSites()
        src.loadPkgOptions()
-       src.loadDocChanges()
+       src.changes.load(src)
        src.loadSuggestedUpdates()
        src.loadUserDefinedVars()
        src.loadTools()
@@ -148,308 +141,6 @@ func (src *Pkgsrc) loadPkgOptions() {
        }
 }
 
-func (src *Pkgsrc) loadDocChanges() {
-       docDir := src.File("doc")
-       files := src.ReadDir("doc")
-       if len(files) == 0 {
-               G.Logger.TechFatalf(docDir, "Cannot be read for loading the package changes.")
-       }
-
-       var filenames []RelPath
-       for _, file := range files {
-               filename := file.Name()
-               // Files before 2011 are too far in the past to be still relevant today.
-               if matches(filename, `^CHANGES-20\d\d$`) && filename >= "CHANGES-2011" {
-                       filenames = append(filenames, NewRelPathString(filename))
-               }
-       }
-
-       src.LastChange = make(map[PkgsrcPath]*Change)
-       for _, filename := range filenames {
-               changes := src.loadDocChangesFromFile(docDir.JoinNoClean(filename), false)
-               for _, change := range changes {
-                       src.LastChange[change.Pkgpath] = change
-                       if change.Action == Renamed || change.Action == Moved {
-                               src.LastChange[change.Target()] = change
-                       }
-               }
-       }
-
-       src.checkRemovedAfterLastFreeze()
-}
-
-func (src *Pkgsrc) loadDocChangesFromFile(filename CurrPath, direct bool) []*Change {
-
-       warn := direct || G.CheckGlobal && !G.Wip
-
-       // Each date in the file should be from the same year as the filename says.
-       // This check has been added in 2018.
-       // For years earlier than 2018 pkglint doesn't care because it's not a big issue anyway.
-       year := ""
-       if _, yyyy := match1(filename.Base().String(), `-(\d\d\d\d)$`); yyyy >= "2018" {
-               year = yyyy
-       }
-       thorough := direct || G.CheckGlobal || year >= "2020" // For performance reasons
-
-       latest := make(map[PkgsrcPath]*Change)
-
-       infra := false
-       lines := Load(filename, MustSucceed|NotEmpty)
-       var changes []*Change
-       for _, line := range lines.Lines {
-
-               if hasPrefix(line.Text, "\tmk/") {
-                       infra = true
-                       if hasPrefix(line.Text, "\tmk/bsd.pkg.mk: started freeze for") {
-                               if m, date := match1(line.Text, `(\d\d\d\d-\d\d-\d\d)\]$`); m {
-                                       src.LastFreezeStart = date
-                                       src.LastFreezeEnd = ""
-                               }
-                       } else if hasPrefix(line.Text, "\tmk/bsd.pkg.mk: freeze ended for") {
-                               if m, date := match1(line.Text, `(\d\d\d\d-\d\d-\d\d)\]$`); m {
-                                       src.LastFreezeEnd = date
-                               }
-                       }
-               }
-               if infra {
-                       if hasSuffix(line.Text, "]") {
-                               infra = false
-                       }
-                       continue
-               }
-
-               change := src.parseDocChange(line, warn)
-               if change == nil {
-                       continue
-               }
-
-               changes = append(changes, change)
-
-               if !warn {
-                       continue
-               }
-
-               if thorough {
-                       src.checkChangeVersion(change, latest, line)
-                       src.checkChangeDate(filename, year, change, line, changes)
-               }
-       }
-
-       return changes
-}
-
-func (src *Pkgsrc) checkChangeVersion(change *Change, latest map[PkgsrcPath]*Change, line *Line) {
-       switch change.Action {
-
-       case Added:
-               src.checkChangeVersionNumber(change, line)
-               existing := latest[change.Pkgpath]
-               if existing != nil && existing.Version() == change.Version() {
-                       line.Warnf("Package %q was already added in %s.",
-                               change.Pkgpath.String(), line.RelLocation(existing.Location))
-               }
-               latest[change.Pkgpath] = change
-
-       case Updated:
-               src.checkChangeVersionNumber(change, line)
-               existing := latest[change.Pkgpath]
-               if existing != nil && pkgver.Compare(change.Version(), existing.Version()) <= 0 {
-                       line.Warnf("Updating %q from %s in %s to %s should increase the version number.",
-                               change.Pkgpath.String(), existing.Version(), line.RelLocation(existing.Location), change.Version())
-               }
-               latest[change.Pkgpath] = change
-
-       case Downgraded:
-               src.checkChangeVersionNumber(change, line)
-               existing := latest[change.Pkgpath]
-               if existing != nil && pkgver.Compare(change.Version(), existing.Version()) >= 0 {
-                       line.Warnf("Downgrading %q from %s in %s to %s should decrease the version number.",
-                               change.Pkgpath.String(), existing.Version(), line.RelLocation(existing.Location), change.Version())
-               }
-               latest[change.Pkgpath] = change
-
-       default:
-               latest[change.Pkgpath] = nil
-       }
-}
-
-func (src *Pkgsrc) checkChangeVersionNumber(change *Change, line *Line) {
-       version := change.Version()
-
-       switch {
-       case !textproc.NewLexer(version).TestByteSet(textproc.Digit):
-               line.Warnf("Version number %q should start with a digit.", version)
-
-       // See rePkgname for the regular expression.
-       case !matches(version, `^([0-9][.\-0-9A-Z_a-z]*)$`):
-               line.Warnf("Malformed version number %q.", version)
-       }
-}
-
-func (src *Pkgsrc) checkChangeDate(filename CurrPath, year string, change *Change, line *Line, changes []*Change) {
-       if year != "" && change.Date[0:4] != year {
-               line.Warnf("Year %q for %s does not match the filename %s.",
-                       change.Date[0:4], change.Pkgpath.String(), line.Rel(filename))
-       }
-
-       if len(changes) >= 2 && year != "" {
-               if prev := changes[len(changes)-2]; change.Date < prev.Date {
-                       line.Warnf("Date %q for %s is earlier than %q in %s.",
-                               change.Date, change.Pkgpath.String(), prev.Date, line.RelLocation(prev.Location))
-                       line.Explain(
-                               "The entries in doc/CHANGES should be in chronological order, and",
-                               "all dates are assumed to be in the UTC timezone, to prevent time",
-                               "warps.",
-                               "",
-                               "To fix this, determine which of the involved dates are correct",
-                               "and which aren't.",
-                               "",
-                               "To prevent this kind of mistakes in the future,",
-                               "make sure that your system time is correct and run",
-                               sprintf("%q", bmake("cce")),
-                               "to commit the changes entry.")
-               }
-       }
-}
-
-func (*Pkgsrc) parseDocChange(line *Line, warn bool) *Change {
-       lex := textproc.NewLexer(line.Text)
-
-       space := lex.NextHspace()
-       if space == "" {
-               return nil
-       }
-
-       if space != "\t" {
-               if warn {
-                       line.Warnf("Package changes should be indented using a single tab, not %q.", space)
-                       line.Explain(
-                               "To avoid this formatting mistake in the future, just run",
-                               sprintf("%q", bmake("cce")),
-                               "after committing the update to the package.")
-               }
-
-               return nil
-       }
-
-       invalid := func() *Change {
-               if warn {
-                       line.Warnf("Invalid doc/CHANGES line: %s", line.Text)
-                       line.Explain(
-                               "See mk/misc/developer.mk for the rules.",
-                               "",
-                               "To generate these entries automatically, run",
-                               sprintf("%q.", bmakeHelp("cce")))
-               }
-               return nil
-       }
-
-       f := strings.Fields(lex.Rest())
-       n := len(f)
-       if n > 1 && hasSuffix(f[0], ":") {
-               return nil
-       }
-       if n == 0 {
-               return invalid()
-       }
-
-       action := ParseChangeAction(f[0])
-       var pkgpath, author, date string
-       if n > 1 {
-               pkgpath = f[1]
-               date = f[n-1]
-       }
-       if n > 2 {
-               author = f[n-2]
-       }
-
-       author, date = (*Pkgsrc).parseAuthorAndDate(nil, author, date)
-       if author == "" {
-               return invalid()
-       }
-
-       switch {
-       case
-               action == Added && f[2] == "version" && n == 6,
-               action == Updated && f[2] == "to" && n == 6,
-               action == Downgraded && f[2] == "to" && n == 6,
-               action == Removed && ((f[2] == "successor" || f[2] == "version") && n == 6 || n == 4),
-               (action == Renamed || action == Moved) && f[2] == "to" && n == 6:
-               return &Change{
-                       Location: line.Location,
-                       Action:   action,
-                       Pkgpath:  NewPkgsrcPath(NewPath(intern(pkgpath))),
-                       target:   intern(condStr(n == 6, f[3], "")),
-                       Author:   intern(author),
-                       Date:     intern(date),
-               }
-       }
-
-       return invalid()
-}
-
-// parseAuthorAndDate parses the author and date from a line in doc/CHANGES.
-func (*Pkgsrc) parseAuthorAndDate(author, date string) (string, string) {
-       alex := textproc.NewLexer(author)
-       if !alex.SkipByte('[') {
-               return "", ""
-       }
-       author = alex.NextBytesSet(textproc.AlnumU)
-       if !alex.EOF() {
-               return "", ""
-       }
-
-       isDigit := func(b byte) bool { return '0' <= b && b <= '9' }
-
-       if len(date) == 11 &&
-               isDigit(date[0]) &&
-               isDigit(date[1]) &&
-               isDigit(date[2]) &&
-               isDigit(date[3]) &&
-               date[4] == '-' &&
-               isDigit(date[5]) &&
-               isDigit(date[6]) &&
-               10*(date[5]-'0')+(date[6]-'0') >= 1 &&
-               10*(date[5]-'0')+(date[6]-'0') <= 12 &&
-               date[7] == '-' &&
-               isDigit(date[8]) &&
-               isDigit(date[9]) &&
-               10*(date[8]-'0')+(date[9]-'0') >= 1 &&
-               10*(date[8]-'0')+(date[9]-'0') <= 31 &&
-               date[10] == ']' {
-               date = date[:10]
-               return author, date
-       }
-
-       return "", ""
-}
-
-func (src *Pkgsrc) checkRemovedAfterLastFreeze() {
-       if src.LastFreezeStart == "" || G.Wip || !G.CheckGlobal {
-               return
-       }
-
-       var wrong []*Change
-       for pkgpath, change := range src.LastChange {
-               switch change.Action {
-               case Added, Updated, Downgraded:
-                       if !src.File(pkgpath).IsDir() {
-                               wrong = append(wrong, change)
-                       }
-               }
-       }
-
-       sort.Slice(wrong, func(i, j int) bool { return wrong[i].IsAbove(wrong[j]) })
-
-       for _, change := range wrong {
-               // The original line of the change is not available anymore.
-               // Therefore, it is necessary to load the whole file again.
-               lines := Load(change.Location.Filename, MustSucceed)
-               line := lines.Lines[change.Location.lineno-1]
-               line.Errorf("Package %s must either exist or be marked as removed.", change.Pkgpath.String())
-       }
-}
-
 func (src *Pkgsrc) loadSuggestedUpdates() {
        src.suggestedUpdates = src.parseSuggestedUpdates(Load(src.File("doc/TODO"), MustSucceed))
        src.suggestedWipUpdates = src.parseSuggestedUpdates(Load(src.File("wip/TODO"), NotEmpty))
@@ -955,8 +646,9 @@ func (src *Pkgsrc) loadDefaultBuildDefs(
 // name with repl.
 //
 // Example:
-//  Latest("lang", `^php[0-9]+$`, "../../lang/$0")
-//      => "../../lang/php72"
+//
+//     Latest("lang", `^php[0-9]+$`, "../../lang/$0")
+//         => "../../lang/php72"
 func (src *Pkgsrc) Latest(category PkgsrcPath, re regex.Pattern, repl string) string {
        versions := src.ListVersions(category, re, repl, true)
 
@@ -971,8 +663,9 @@ func (src *Pkgsrc) Latest(category Pkgsr
 // of them, properly sorted from early to late.
 //
 // Example:
-//  ListVersions("lang", `^php[0-9]+$`, "php-$0")
-//      => {"php-53", "php-56", "php-73"}
+//
+//     ListVersions("lang", `^php[0-9]+$`, "php-$0")
+//         => {"php-53", "php-56", "php-73"}
 func (src *Pkgsrc) ListVersions(category PkgsrcPath, re regex.Pattern, repl string, errorIfEmpty bool) []string {
        if G.Testing {
                // Regular expression must be anchored at both ends, to avoid typos.
@@ -1304,7 +997,8 @@ func (src *Pkgsrc) Relpath(from, to Curr
 // File resolves a filename relative to the pkgsrc top directory.
 //
 // Example:
-//  NewPkgsrc("/usr/pkgsrc").File("distfiles") => "/usr/pkgsrc/distfiles"
+//
+//     NewPkgsrc("/usr/pkgsrc").File("distfiles") => "/usr/pkgsrc/distfiles"
 func (src *Pkgsrc) File(relativeName PkgsrcPath) CurrPath {
        cleaned := NewRelPath(relativeName.AsPath()).Clean()
        if cleaned == "." {
@@ -1327,7 +1021,8 @@ func (src *Pkgsrc) FilePkg(rel PackagePa
 // Rel returns the path of `filename`, relative to the pkgsrc top directory.
 //
 // Example:
-//  NewPkgsrc("/usr/pkgsrc").Rel("/usr/pkgsrc/distfiles") => "distfiles"
+//
+//     NewPkgsrc("/usr/pkgsrc").Rel("/usr/pkgsrc/distfiles") => "distfiles"
 func (src *Pkgsrc) Rel(filename CurrPath) PkgsrcPath {
        return NewPkgsrcPath(src.Relpath(src.topdir, filename).AsPath())
 }
@@ -1348,81 +1043,3 @@ func (src *Pkgsrc) IsWip(filename CurrPa
        rel := src.Rel(filename)
        return rel.HasPrefixPath("wip")
 }
-
-// Change describes a modification to a single package, from the doc/CHANGES-* files.
-type Change struct {
-       Location Location
-       Action   ChangeAction // Added, Updated, Downgraded, Renamed, Moved, Removed
-       Pkgpath  PkgsrcPath   // For renamed or moved packages, the previous PKGPATH
-       target   string       // The path or version number, depending on the action
-       Author   string
-       Date     string
-}
-
-// Version returns the version number for an Added, Updated or Downgraded package.
-func (ch *Change) Version() string {
-       assert(ch.Action == Added || ch.Action == Updated || ch.Action == Downgraded)
-       return ch.target
-}
-
-// Target returns the target PKGPATH for a Renamed or Moved package.
-func (ch *Change) Target() PkgsrcPath {
-       assert(ch.Action == Renamed || ch.Action == Moved)
-       return NewPkgsrcPath(NewPath(ch.target))
-}
-
-// SuccessorOrVersion returns the successor for a Removed package,
-// or the version number of its last appearance.
-// As of 2020-10-06, no cross-validation is done on this field though.
-func (ch *Change) SuccessorOrVersion() string {
-       assert(ch.Action == Removed)
-       return ch.target
-}
-
-func (ch *Change) IsAbove(other *Change) bool {
-       if ch.Date != other.Date {
-               return ch.Date < other.Date
-       }
-       return ch.Location.lineno < other.Location.lineno
-}
-
-type ChangeAction uint8
-
-const (
-       Added ChangeAction = 1 + iota
-       Updated
-       Downgraded
-       Renamed
-       Moved
-       Removed
-)
-
-func ParseChangeAction(s string) ChangeAction {
-       switch s {
-       case "Added":
-               return Added
-       case "Updated":
-               return Updated
-       case "Downgraded":
-               return Downgraded
-       case "Renamed":
-               return Renamed
-       case "Moved":
-               return Moved
-       case "Removed":
-               return Removed
-       }
-       return 0
-}
-
-func (ca ChangeAction) String() string {
-       return [...]string{"", "Added", "Updated", "Downgraded", "Renamed", "Moved", "Removed"}[ca]
-}
-
-// SuggestedUpdate describes a desired package update, from the doc/TODO file.
-type SuggestedUpdate struct {
-       Line    Location
-       Pkgname string
-       Version string
-       Comment string
-}

Index: pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go
diff -u pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.56 pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.57
--- pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go:1.56   Sat Nov 19 10:51:07 2022
+++ pkgsrc/pkgtools/pkglint/files/pkgsrc_test.go        Sun Jan 29 13:36:31 2023
@@ -4,7 +4,6 @@ import (
        "gopkg.in/check.v1"
        "os"
        "path/filepath"
-       "strings"
 )
 
 func (s *Suite) Test_Pkgsrc__frozen(c *check.C) {
@@ -15,7 +14,7 @@ func (s *Suite) Test_Pkgsrc__frozen(c *c
                "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2018Q2 branch [freezer 2018-03-25]")
        t.FinishSetUp()
 
-       t.CheckEquals(G.Pkgsrc.LastFreezeStart, "2018-03-25")
+       t.CheckEquals(G.Pkgsrc.changes.LastFreezeStart, "2018-03-25")
 }
 
 func (s *Suite) Test_Pkgsrc__not_frozen(c *check.C) {
@@ -27,8 +26,8 @@ func (s *Suite) Test_Pkgsrc__not_frozen(
                "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2018Q2 branch [freezer 2018-03-27]")
        t.FinishSetUp()
 
-       t.CheckEquals(G.Pkgsrc.LastFreezeStart, "2018-03-25")
-       t.CheckEquals(G.Pkgsrc.LastFreezeEnd, "2018-03-27")
+       t.CheckEquals(G.Pkgsrc.changes.LastFreezeStart, "2018-03-25")
+       t.CheckEquals(G.Pkgsrc.changes.LastFreezeEnd, "2018-03-27")
 }
 
 func (s *Suite) Test_Pkgsrc__frozen_with_typo(c *check.C) {
@@ -40,7 +39,7 @@ func (s *Suite) Test_Pkgsrc__frozen_with
                "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2018Q2 branch [freezer 2018-03-25")
        t.FinishSetUp()
 
-       t.CheckEquals(G.Pkgsrc.LastFreezeStart, "")
+       t.CheckEquals(G.Pkgsrc.changes.LastFreezeStart, "")
 }
 
 func (s *Suite) Test_Pkgsrc__caching(c *check.C) {
@@ -110,543 +109,6 @@ func (s *Suite) Test_Pkgsrc_loadPkgOptio
                "ERROR: ~/mk/defaults/options.description:4: Invalid line format: >>>>> Merge conflict")
 }
 
-func (s *Suite) Test_Pkgsrc_loadDocChanges(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpPkgsrc()
-       t.CreateFileLines("doc/CHANGES-2018",
-               CvsID,
-               "",
-               "\tUpdated pkgpath to 1.0 [author 2018-01-01]",
-               "\tRenamed pkgpath to new-pkg [author 2018-02-01]",
-               "\tMoved pkgpath to category/new-pkg [author 2018-03-01]")
-       t.FinishSetUp()
-
-       t.CheckEquals(G.Pkgsrc.LastChange["pkgpath"].Action, Moved)
-}
-
-func (s *Suite) Test_Pkgsrc_loadDocChanges__not_found(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpPkgsrc()
-       t.Remove("doc/CHANGES-2018")
-       t.Remove("doc/TODO")
-       t.Remove("doc")
-
-       t.ExpectFatal(
-               t.FinishSetUp,
-               "FATAL: ~/doc: Cannot be read for loading the package changes.")
-}
-
-func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile(c *check.C) {
-       t := s.Init(c)
-
-       t.CreateFileLines("doc/CHANGES-2018",
-               "\tAdded category/package version 1.0 [author1 2015-01-01]", // Wrong year
-               "\tUpdated category/package to 1.5 [author2 2018-01-02]",
-               "\tRenamed category/package to category/pkg [author3 2018-01-03]",
-               "\tMoved category/package to other/package [author4 2018-01-04]",
-               "\tRemoved category/package [author5 2018-01-09]", // Too far in the future
-               "\tRemoved category/package successor category/package2 [author6 2018-01-06]",
-               "\tDowngraded category/package to 1.2 [author7 2018-01-07]",
-               "\tReworked category/package to 1.2 [author8 2018-01-08]",
-               "",
-               "\ttoo few fields",
-               "\ttoo many many many many many fields",
-               "\tmissing brackets around author",
-               "\tAdded another [new package]",
-               "",
-               "\tmk/bsd.pkg.mk: freeze ended for branch pkgsrc-2019Q2", // missing date
-               "\tmk/bsd.pkg.mk: freeze ended for branch pkgsrc-2019Q2 [thawer 2019-07-01]",
-               "",
-               "Normal paragraph.")
-
-       changes := G.Pkgsrc.loadDocChangesFromFile(t.File("doc/CHANGES-2018"), true)
-
-       t.CheckDeepEquals(
-               changes, []*Change{
-                       {changes[0].Location,
-                               Added, "category/package", "1.0",
-                               "author1", "2015-01-01"},
-                       {changes[1].Location,
-                               Updated, "category/package", "1.5",
-                               "author2", "2018-01-02"},
-                       {changes[2].Location,
-                               Renamed, "category/package", "category/pkg",
-                               "author3", "2018-01-03"},
-                       {changes[3].Location,
-                               Moved, "category/package", "other/package",
-                               "author4", "2018-01-04"},
-                       {changes[4].Location,
-                               Removed, "category/package", "",
-                               "author5", "2018-01-09"},
-                       {changes[5].Location,
-                               Removed, "category/package", "category/package2",
-                               "author6", "2018-01-06"},
-                       {changes[6].Location,
-                               Downgraded, "category/package", "1.2",
-                               "author7", "2018-01-07"}})
-
-       t.CheckOutputLines(
-               "WARN: ~/doc/CHANGES-2018:1: Year \"2015\" for category/package does not match the filename CHANGES-2018.",
-               "WARN: ~/doc/CHANGES-2018:6: Date \"2018-01-06\" for category/package is earlier than \"2018-01-09\" in line 5.",
-               "WARN: ~/doc/CHANGES-2018:8: Invalid doc/CHANGES line: \tReworked category/package to 1.2 [author8 2018-01-08]",
-               "WARN: ~/doc/CHANGES-2018:10: Invalid doc/CHANGES line: \ttoo few fields",
-               "WARN: ~/doc/CHANGES-2018:11: Invalid doc/CHANGES line: \ttoo many many many many many fields",
-               "WARN: ~/doc/CHANGES-2018:12: Invalid doc/CHANGES line: \tmissing brackets around author",
-               "WARN: ~/doc/CHANGES-2018:13: Invalid doc/CHANGES line: \tAdded another [new package]")
-}
-
-func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile__not_found(c *check.C) {
-       t := s.Init(c)
-
-       t.ExpectFatal(
-               func() { G.Pkgsrc.loadDocChangesFromFile(t.File("doc/CHANGES-2018"), false) },
-               "FATAL: ~/doc/CHANGES-2018: Cannot be read.")
-}
-
-// Since package authors for pkgsrc-wip cannot necessarily commit to
-// main pkgsrc, don't warn about unsorted doc/CHANGES lines.
-// Only pkgsrc main committers can fix these.
-func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile__wip_suppresses_warnings(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpPackage("wip/package")
-       t.CreateFileLines("doc/CHANGES-2018",
-               CvsID,
-               "",
-               "Changes to the packages collection and infrastructure in 2018:",
-               "",
-               "\tUpdated sysutils/checkperms to 1.10 [rillig 2018-01-05]",
-               "\tUpdated sysutils/checkperms to 1.11 [rillig 2018-01-01]",
-               "\t\tWrong indentation",
-               "\tInvalid pkgpath to 1.16 [rillig 2019-06-16]")
-
-       t.Main("-Cglobal", "-Wall", "wip/package")
-
-       t.CheckOutputLines(
-               "Looks fine.")
-}
-
-// When a single package is checked, only the lines from doc/CHANGES
-// that are related to that package are shown. The others are too
-// unrelated.
-func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile__default(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpPackage("category/package")
-       t.CreateFileLines("doc/CHANGES-2018",
-               CvsID,
-               "",
-               "Changes to the packages collection and infrastructure in 2018:",
-               "",
-               "\tUpdated sysutils/checkperms to 1.10 [rillig 2018-01-05]",
-               "\tUpdated sysutils/checkperms to 1.11 [rillig 2018-01-01]",
-               "\t\tWrong indentation",
-               "\tInvalid pkgpath to 1.16 [rillig 2019-06-16]",
-               "\tUpdated category/package to 2.0 [rillig 2019-07-24]")
-       t.CreateFileLines("Makefile",
-               MkCvsID)
-
-       t.Main("category/package")
-
-       t.CheckOutputLines(
-               "WARN: ~/category/package/Makefile:3: The package is being downgraded from 2.0 (see ../../doc/CHANGES-2018:9) to 1.0.",
-               "1 warning found.",
-               t.Shquote("(Run \"pkglint -e %s\" to show explanations.)", "category/package"))
-
-       // Only when the global checks are enabled, the errors from doc/CHANGES are shown.
-       t.Main("-Cglobal", "-Wall", ".")
-
-       t.CheckOutputLines(
-               "WARN: ~/doc/CHANGES-2018:6: Date \"2018-01-01\" for sysutils/checkperms is earlier than \"2018-01-05\" in line 5.",
-               "WARN: ~/doc/CHANGES-2018:7: Package changes should be indented using a single tab, not \"\\t\\t\".",
-               "WARN: ~/doc/CHANGES-2018:8: Invalid doc/CHANGES line: \tInvalid pkgpath to 1.16 [rillig 2019-06-16]",
-               "WARN: ~/doc/CHANGES-2018:9: Year \"2019\" for category/package does not match the filename CHANGES-2018.",
-               "4 warnings found.",
-               t.Shquote("(Run \"pkglint -e -Cglobal -Wall %s\" to show explanations.)", "."))
-}
-
-func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile__wrong_indentation(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpPackage("category/package")
-       t.CreateFileLines("doc/CHANGES-2018",
-               CvsID,
-               "",
-               "Changes to the packages collection and infrastructure in 2018:",
-               "",
-               "        Updated sysutils/checkperms to 1.10 [rillig 2018-01-05]",
-               "    \tUpdated sysutils/checkperms to 1.11 [rillig 2018-01-01]")
-
-       t.Main("-Cglobal", "-Wall", "category/package")
-
-       t.CheckOutputLines(
-               "WARN: ~/doc/CHANGES-2018:5: Package changes should be indented using a single tab, not \"        \".",
-               "WARN: ~/doc/CHANGES-2018:6: Package changes should be indented using a single tab, not \"    \\t\".",
-               "2 warnings found.",
-               t.Shquote("(Run \"pkglint -e -Cglobal -Wall %s\" to show explanations.)", "category/package"))
-}
-
-// Once or twice in a decade, changes to the pkgsrc infrastructure are also
-// documented in doc/CHANGES. These entries typically span multiple lines.
-func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile__infrastructure(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpPackage("category/package")
-       t.CreateFileLines("doc/CHANGES-2018",
-               CvsID,
-               "",
-               "Changes to the packages collection and infrastructure in 2018:",
-               "",
-               "\tmk/bsd.pkg.mk: Added new framework for handling packages",
-               "\t\twith multiple MASTER_SITES while fetching the main",
-               "\t\tdistfile directly from GitHub [rillig 2018-01-01]",
-               "\tmk/bsd.pkg.mk: Another infrastructure change [rillig 2018-01-02]")
-
-       t.Main("category/package")
-
-       // For pkglint's purpose, the infrastructure entries are simply ignored
-       // since they do not belong to a single package.
-       t.CheckOutputLines(
-               "Looks fine.")
-}
-
-func (s *Suite) Test_Pkgsrc_loadDocChangesFromFile__old(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpCommandLine("-Cglobal", "-Wall")
-       t.SetUpPkgsrc()
-       t.CreateFileLines("doc/CHANGES-2010",
-               CvsID,
-               "",
-               "Changes to the packages collection and infrastructure in 2015:",
-               "",
-               "\tInvalid line [3 4]")
-       t.CreateFileLines("doc/CHANGES-2015",
-               CvsID,
-               "",
-               "Changes to the packages collection and infrastructure in 2015:",
-               "",
-               "\tUpdated pkgpath to 1.0 [author 2015-07-01]",
-               "\tInvalid line [3 4]",
-               // The date of the below entry is earlier than that of the above entry;
-               // this error is ignored because the 2015 file is too old.
-               "\tUpdated pkgpath to 1.2 [author 2015-02-01]")
-       t.CreateFileLines("doc/CHANGES-2018",
-               CvsID,
-               "",
-               "Changes to the packages collection and infrastructure in 2018:",
-               "",
-               "\tUpdated pkgpath to 1.0 [author date]",
-               "\tUpdated pkgpath to 1.0 [author d]")
-       t.FinishSetUp()
-
-       // The 2010 file is so old that it is skipped completely.
-       // The 2015 file is so old that the date is not checked.
-       // Since 2018, each date in the file must match the filename.
-       t.CheckOutputLines(
-               "WARN: ~/doc/CHANGES-2015:6: Invalid doc/CHANGES line: \tInvalid line [3 4]",
-               "WARN: ~/doc/CHANGES-2018:5: Invalid doc/CHANGES line: \tUpdated pkgpath to 1.0 [author date]",
-               "WARN: ~/doc/CHANGES-2018:6: Invalid doc/CHANGES line: \tUpdated pkgpath to 1.0 [author d]")
-}
-
-func (s *Suite) Test_Pkgsrc_checkChangeVersion(c *check.C) {
-       t := s.Init(c)
-
-       t.CreateFileLines("doc/CHANGES-2020",
-               "\tAdded category/package version 1.0 [author1 2020-01-01]",
-               "\tAdded category/package version 1.0 [author1 2020-01-01]",
-               "\tAdded category/package version 2.3 [author1 2020-01-01]",
-               "\tUpdated category/package to 0.9 [author1 2020-01-01]",
-               "\tDowngraded category/package to 1.0 [author1 2020-01-01]",
-               "\tDowngraded category/package to 0.8 [author 2020-01-01]",
-               "\tRenamed category/package to category/renamed [author1 2020-01-01]",
-               "\tMoved category/package to other/renamed [author1 2020-01-01]")
-       t.Chdir("doc")
-
-       G.Pkgsrc.loadDocChangesFromFile("CHANGES-2020", true)
-
-       // In line 3 there is no warning about the repeated addition since
-       // the multi-packages (Lua, PHP, Python) may add a package in
-       // several versions to the same PKGPATH.
-       t.CheckOutputLines(
-               "WARN: CHANGES-2020:2: Package \"category/package\" was already added in line 1.",
-               "WARN: CHANGES-2020:4: Updating \"category/package\" from 2.3 in line 3 to 0.9 should increase the version number.",
-               "WARN: CHANGES-2020:5: Downgrading \"category/package\" from 0.9 in line 4 to 1.0 should decrease the version number.")
-}
-
-func (s *Suite) Test_Pkgsrc_checkChangeVersionNumber(c *check.C) {
-       t := s.Init(c)
-
-       t.CreateFileLines("doc/CHANGES-2020",
-               "\tAdded category/package version v1 [author1 2020-01-01]",
-               "\tUpdated category/package to v2 [author1 2020-01-01]",
-               "\tDowngraded category/package to v2 [author1 2020-01-01]",
-               "\tUpdated category/package to 2020/03 [author1 2020-01-01]")
-       t.Chdir("doc")
-
-       G.Pkgsrc.loadDocChangesFromFile("CHANGES-2020", true)
-
-       t.CheckOutputLines(
-               "WARN: CHANGES-2020:1: Version number \"v1\" should start with a digit.",
-               "WARN: CHANGES-2020:2: Version number \"v2\" should start with a digit.",
-               "WARN: CHANGES-2020:3: Version number \"v2\" should start with a digit.",
-               "WARN: CHANGES-2020:3: Downgrading \"category/package\" from v2 in line 2 "+
-                       "to v2 should decrease the version number.",
-               "WARN: CHANGES-2020:4: Malformed version number \"2020/03\".")
-}
-
-func (s *Suite) Test_Pkgsrc_parseDocChange(c *check.C) {
-       t := s.Init(c)
-
-       test := func(text string, diagnostics ...string) {
-               line := t.NewLine("doc/CHANGES-2019", 123, text)
-               _ = (*Pkgsrc)(nil).parseDocChange(line, true)
-               t.CheckOutput(diagnostics)
-       }
-
-       test(CvsID,
-               nil...)
-       test("",
-               nil...)
-       test("Changes to the packages collection and infrastructure in 2019:",
-               nil...)
-
-       test("\tAdded something [author date]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tAdded something [author date]")
-
-       test("\tAdded category/package 1.0 [author 2019-11-17]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tAdded category/package 1.0 [author 2019-11-17]")
-
-       test("\t\tToo large indentation",
-               "WARN: doc/CHANGES-2019:123: Package changes should be indented using a single tab, not \"\\t\\t\".")
-       test("\t Too large indentation",
-               "WARN: doc/CHANGES-2019:123: Package changes should be indented using a single tab, not \"\\t \".")
-
-       test("\t",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t")
-       test("\t1",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1")
-       test("\t1 2 3 4",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4")
-       test("\t1 2 3 4 5",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5")
-       test("\t1 2 3 4 5 6",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5 6")
-       test("\t1 2 3 4 5 6 7",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5 6 7")
-       test("\t1 2 [3 4",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 [3 4")
-       test("\t1 2 [3 4]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 [3 4]")
-       test("\tAdded 2 [3 4]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \tAdded 2 [3 4]")
-
-       test("\tAdded pkgpath version 1.0 [author 2019-01-01]",
-               nil...)
-
-       // "to" is wrong
-       test("\tAdded pkgpath to 1.0 [author 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tAdded pkgpath to 1.0 [author 2019-01-01]")
-
-       test("\tUpdated pkgpath to 1.0 [author 2019-01-01]",
-               nil...)
-
-       // "from" is wrong
-       test("\tUpdated pkgpath from 1.0 [author 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tUpdated pkgpath from 1.0 [author 2019-01-01]")
-
-       test("\tDowngraded pkgpath to 1.0 [author 2019-01-01]",
-               nil...)
-
-       // "from" is wrong
-       test("\tDowngraded pkgpath from 1.0 [author 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tDowngraded pkgpath from 1.0 [author 2019-01-01]")
-
-       test("\tRemoved pkgpath [author 2019-01-01]",
-               nil...)
-
-       test("\tRemoved pkgpath successor pkgpath [author 2019-01-01]",
-               nil...)
-
-       // Since 2020-10-06
-       test("\tRemoved pkgpath version 1.3.4 [author 2019-01-01]",
-               nil...)
-
-       // "and" is wrong
-       test("\tRemoved pkgpath and pkgpath [author 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tRemoved pkgpath and pkgpath [author 2019-01-01]")
-
-       test("\tRenamed pkgpath to other [author 2019-01-01]",
-               nil...)
-
-       // "from" is wrong
-       test("\tRenamed pkgpath from previous [author 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tRenamed pkgpath from previous [author 2019-01-01]")
-
-       test("\tMoved pkgpath to other [author 2019-01-01]",
-               nil...)
-
-       // "from" is wrong
-       test("\tMoved pkgpath from previous [author 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tMoved pkgpath from previous [author 2019-01-01]")
-
-       // "Split" is wrong
-       test("\tSplit pkgpath into a and b [author 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tSplit pkgpath into a and b [author 2019-01-01]")
-
-       // Entries ending in a colon are used for infrastructure changes.
-       test("\tmk: remove support for USE_CROSSBASE [author 2016-06-19]",
-               nil...)
-
-       test("\tAdded category/pkgpath version 1.0 [author-dash 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tAdded category/pkgpath version 1.0 [author-dash 2019-01-01]")
-
-       // The word 'version' must only appear with 'Added', not with 'Updated'.
-       test("\tUpdated category/pkgpath to version 1.0 [author 2019-01-01]",
-               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
-                       "\tUpdated category/pkgpath to version 1.0 [author 2019-01-01]")
-}
-
-func (s *Suite) Test_Pkgsrc_parseAuthorAndDate(c *check.C) {
-       t := s.Init(c)
-
-       test := func(dateAndAuthor string, expectedAuthor, expectedDate string) {
-               fields := strings.Split(dateAndAuthor, " ")
-               authorIn, dateIn := fields[0], fields[1]
-               author, date := (*Pkgsrc).parseAuthorAndDate(nil, authorIn, dateIn)
-               t.CheckEquals(author, expectedAuthor)
-               t.CheckEquals(date, expectedDate)
-       }
-
-       test("[author 20!9-01-01]", "", "") // bad digit '!' in year
-       test("[author x019-01-01]", "", "") // bad digit 'x' in year
-       test("[author 2x19-01-01]", "", "") // bad digit 'x' in year
-       test("[author 20x9-01-01]", "", "") // bad digit 'x' in year
-       test("[author 201x-01-01]", "", "") // bad digit 'x' in year
-
-       test("[author 2019/01-01]", "", "") // bad separator '/'
-
-       test("[author 2019-x0-01]", "", "") // bad digit 'x' in month
-       test("[author 2019-0x-01]", "", "") // bad digit 'x' in month
-       test("[author 2019-00-01]", "", "") // bad month '00'
-       test("[author 2019-13-01]", "", "") // bad month '13'
-
-       test("[author 2019-01/01]", "", "") // bad separator '/'
-
-       test("[author 2019-01-x0]", "", "") // bad digit 'x' in day
-       test("[author 2019-01-0x]", "", "") // bad digit 'x' in day
-       test("[author 2019-01-00]", "", "") // bad day '00'
-       test("[author 2019-01-32]", "", "") // bad day '32'
-       // No leap year detection, to keep the code fast.
-       test("[author 2019-02-29]", "author", "2019-02-29") // 2019 is not a leap year.
-
-       test("[author 2019-01-01", "", "")  // missing trailing ']'
-       test("[author 2019-01-01+", "", "") // trailing '+' instead of ']'
-
-       test("[author 2019-01-01]", "author", "2019-01-01")
-}
-
-func (s *Suite) Test_Pkgsrc_checkRemovedAfterLastFreeze(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpCommandLine("-Wall", "--source")
-       t.CreateFileLines("doc/CHANGES-2019",
-               CvsID,
-               "",
-               "\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
-               "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2019Q1 branch [freezer 2019-06-21]",
-               "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2019Q1 branch [freezer 2019-06-25]",
-               "\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
-               "\tAdded category/added-after version 1.0 [updater 2019-07-01]",
-               "\tMoved category/moved-from to category/moved-to [author 2019-07-02]",
-               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]",
-               "\tUpdated category/still-there to 1.0 [updater 2019-07-04]")
-       t.SetUpPackage("category/still-there")
-       t.FinishSetUp()
-
-       // No error message since -Cglobal is not given.
-       t.CheckOutputEmpty()
-}
-
-func (s *Suite) Test_Pkgsrc_checkRemovedAfterLastFreeze__check_global(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpCommandLine("-Wall", "-Cglobal", "--source")
-       t.CreateFileLines("doc/CHANGES-2019",
-               CvsID,
-               "",
-               "\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
-               "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2019Q1 branch [freezer 2019-06-21]",
-               "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2019Q1 branch [freezer 2019-06-25]",
-               "\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
-               "\tAdded category/added-after version 1.0 [updater 2019-07-01]",
-               "\tMoved category/moved-from to category/moved-to [author 2019-07-02]",
-               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]",
-               "\tUpdated category/still-there to 1.0 [updater 2019-07-04]")
-       t.SetUpPackage("category/still-there")
-       t.FinishSetUp()
-
-       // It doesn't matter whether the last visible package change was before
-       // or after the latest freeze. The crucial point is that the most
-       // interesting change is the invisible one, which is the removal.
-       // And for finding the removal reliably, it doesn't matter how long ago
-       // the last package change was.
-
-       t.CheckOutputLines(
-               ">\t\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
-               "ERROR: ~/doc/CHANGES-2019:3: Package category/updated-before "+
-                       "must either exist or be marked as removed.",
-               "",
-               ">\t\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
-               "ERROR: ~/doc/CHANGES-2019:6: Package category/updated-after "+
-                       "must either exist or be marked as removed.",
-               "",
-               ">\t\tAdded category/added-after version 1.0 [updater 2019-07-01]",
-               "ERROR: ~/doc/CHANGES-2019:7: Package category/added-after "+
-                       "must either exist or be marked as removed.",
-               "",
-               ">\t\tDowngraded category/downgraded to 1.0 [author 2019-07-03]",
-               "ERROR: ~/doc/CHANGES-2019:9: Package category/downgraded "+
-                       "must either exist or be marked as removed.")
-}
-
-func (s *Suite) Test_Pkgsrc_checkRemovedAfterLastFreeze__wip(c *check.C) {
-       t := s.Init(c)
-
-       t.SetUpPackage("wip/package")
-       t.CreateFileLines("doc/CHANGES-2019",
-               CvsID,
-               "",
-               "\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
-               "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2019Q1 branch [freezer 2019-06-21]",
-               "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2019Q1 branch [freezer 2019-06-25]",
-               "\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
-               "\tAdded category/added-after version 1.0 [updater 2019-07-01]",
-               "\tMoved category/moved-from to category/moved-to [author 2019-07-02]",
-               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]")
-
-       t.Main("-Wall", "--source", "wip/package")
-
-       // Since the first argument is in pkgsrc-wip, the check for doc/CHANGES
-       // is skipped. It may well be that a pkgsrc-wip developer doesn't have
-       // write access to main pkgsrc, and therefore cannot fix doc/CHANGES.
-
-       t.CheckOutputLines(
-               "Looks fine.")
-}
-
 func (s *Suite) Test_Pkgsrc_parseSuggestedUpdates(c *check.C) {
        t := s.Init(c)
 
@@ -1579,78 +1041,3 @@ func (s *Suite) Test_Pkgsrc_FilePkg(c *c
        test("../../category/package", "category/package")
        test("../../../something", "")
 }
-
-func (s *Suite) Test_Change_Version(c *check.C) {
-       t := s.Init(c)
-
-       loc := Location{"doc/CHANGES-2019", 5}
-       added := Change{loc, Added, "category/path", "1.0", "author", "2019-01-01"}
-       updated := Change{loc, Updated, "category/path", "1.0", "author", "2019-01-01"}
-       downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
-       removed := Change{loc, Removed, "category/path", "1.0", "author", "2019-01-01"}
-
-       t.CheckEquals(added.Version(), "1.0")
-       t.CheckEquals(updated.Version(), "1.0")
-       t.CheckEquals(downgraded.Version(), "1.0")
-       t.ExpectAssert(func() { removed.Version() })
-}
-
-func (s *Suite) Test_Change_Target(c *check.C) {
-       t := s.Init(c)
-
-       loc := Location{"doc/CHANGES-2019", 5}
-       renamed := Change{loc, Renamed, "category/path", "category/other", "author", "2019-01-01"}
-       moved := Change{loc, Moved, "category/path", "category/other", "author", "2019-01-01"}
-       downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
-
-       t.CheckEquals(renamed.Target(), NewPkgsrcPath("category/other"))
-       t.CheckEquals(moved.Target(), NewPkgsrcPath("category/other"))
-       t.ExpectAssert(func() { downgraded.Target() })
-}
-
-func (s *Suite) Test_Change_SuccessorOrVersion(c *check.C) {
-       t := s.Init(c)
-
-       loc := Location{"doc/CHANGES-2019", 5}
-       removed := Change{loc, Removed, "category/path", "", "author", "2019-01-01"}
-       removedSucc := Change{loc, Removed, "category/path", "category/successor", "author", "2019-01-01"}
-       removedVersion := Change{loc, Removed, "category/path", "1.3.4", "author", "2019-01-01"}
-       downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
-
-       t.CheckEquals(removed.SuccessorOrVersion(), "")
-       t.CheckEquals(removedSucc.SuccessorOrVersion(), "category/successor")
-       t.CheckEquals(removedVersion.SuccessorOrVersion(), "1.3.4")
-       t.ExpectAssert(func() { downgraded.SuccessorOrVersion() })
-}
-
-func (s *Suite) Test_Change_IsAbove(c *check.C) {
-       t := s.Init(c)
-
-       var changes = []*Change{
-               {Location{"", 1}, 0, "", "", "", "2011-07-01"},
-               {Location{"", 2}, 0, "", "", "", "2011-07-01"},
-               {Location{"", 1}, 0, "", "", "", "2011-07-02"}}
-
-       test := func(i int, chi *Change, j int, chj *Change) {
-               actual := chi.IsAbove(chj)
-               expected := i < j
-               if actual != expected {
-                       t.CheckDeepEquals(
-                               []interface{}{i, *chi, j, *chj, actual},
-                               []interface{}{i, *chi, j, *chj, expected})
-               }
-       }
-
-       for i, chi := range changes {
-               for j, chj := range changes {
-                       test(i, chi, j, chj)
-               }
-       }
-}
-
-func (s *Suite) Test_ChangeAction_String(c *check.C) {
-       t := s.Init(c)
-
-       t.CheckEquals(Added.String(), "Added")
-       t.CheckEquals(Removed.String(), "Removed")
-}

Index: pkgsrc/pkgtools/pkglint/files/plist.go
diff -u pkgsrc/pkgtools/pkglint/files/plist.go:1.63 pkgsrc/pkgtools/pkglint/files/plist.go:1.64
--- pkgsrc/pkgtools/pkglint/files/plist.go:1.63 Thu Jul 28 06:37:04 2022
+++ pkgsrc/pkgtools/pkglint/files/plist.go      Sun Jan 29 13:36:31 2023
@@ -777,11 +777,13 @@ func NewPlistRank(basename RelPath) *Pli
 }
 
 // The ranks among the files are:
-//  PLIST
-//  -> PLIST.common
-//  -> PLIST.common_end
-//  -> { PLIST.OPSYS, PLIST.ARCH }
-//  -> { PLIST.OPSYS.ARCH, PLIST.EMUL_PLATFORM }
+//
+//     PLIST
+//     -> PLIST.common
+//     -> PLIST.common_end
+//     -> { PLIST.OPSYS, PLIST.ARCH }
+//     -> { PLIST.OPSYS.ARCH, PLIST.EMUL_PLATFORM }
+//
 // Files are a later level must not mention files that are already
 // mentioned at an earlier level.
 func (r *PlistRank) MoreGeneric(other *PlistRank) bool {

Index: pkgsrc/pkgtools/pkglint/files/scope.go
diff -u pkgsrc/pkgtools/pkglint/files/scope.go:1.2 pkgsrc/pkgtools/pkglint/files/scope.go:1.3
--- pkgsrc/pkgtools/pkglint/files/scope.go:1.2  Sat Jun 20 07:00:44 2020
+++ pkgsrc/pkgtools/pkglint/files/scope.go      Sun Jan 29 13:36:31 2023
@@ -6,11 +6,13 @@ import "sort"
 // in a certain scope, such as a package or a file.
 //
 // TODO: Decide whether the scope should consider variable assignments
-//  from the pkgsrc infrastructure. For Package.checkGnuConfigureUseLanguages
-//  it would be better to ignore them completely.
+//
+//     from the pkgsrc infrastructure. For Package.checkGnuConfigureUseLanguages
+//     it would be better to ignore them completely.
 //
 // TODO: Merge this code with Var, which defines essentially the
-//  same features.
+//
+//     same features.
 //
 // See also substScope, which already analyzes the possible variable values
 // based on the conditional code paths.
@@ -133,9 +135,9 @@ func (s *Scope) Use(varname string, mkli
 }
 
 // Mentioned returns the first line in which the variable is either:
-//  - defined,
-//  - mentioned in a commented variable assignment,
-//  - mentioned in a documentation comment.
+//   - defined,
+//   - mentioned in a commented variable assignment,
+//   - mentioned in a documentation comment.
 func (s *Scope) Mentioned(varname string) *MkLine {
        if v := s.vs[varname]; v != nil {
                return v.firstDef

Index: pkgsrc/pkgtools/pkglint/files/shell.go
diff -u pkgsrc/pkgtools/pkglint/files/shell.go:1.65 pkgsrc/pkgtools/pkglint/files/shell.go:1.66
--- pkgsrc/pkgtools/pkglint/files/shell.go:1.65 Sat Aug 14 09:46:11 2021
+++ pkgsrc/pkgtools/pkglint/files/shell.go      Sun Jan 29 13:36:31 2023
@@ -437,15 +437,17 @@ func (ck *ShellLineChecker) checkSetE(li
 // files or invalid arguments.
 //
 // Commands that can fail:
-//  echo "hello" > file
-//  sed 's,$, world,,' < input > output
-//  find . -print
-//  wc -l *
+//
+//     echo "hello" > file
+//     sed 's,$, world,,' < input > output
+//     find . -print
+//     wc -l *
 //
 // Commands that cannot fail:
-//  echo "hello"
-//  sed 's,$, world,,'
-//  wc -l
+//
+//     echo "hello"
+//     sed 's,$, world,,'
+//     wc -l
 func (ck *ShellLineChecker) canFail(cmd *MkShCommand) bool {
        simple := cmd.Simple
        if simple == nil {

Index: pkgsrc/pkgtools/pkglint/files/shtokenizer.go
diff -u pkgsrc/pkgtools/pkglint/files/shtokenizer.go:1.25 pkgsrc/pkgtools/pkglint/files/shtokenizer.go:1.26
--- pkgsrc/pkgtools/pkglint/files/shtokenizer.go:1.25   Sat Jan  4 19:53:14 2020
+++ pkgsrc/pkgtools/pkglint/files/shtokenizer.go        Sun Jan 29 13:36:31 2023
@@ -283,12 +283,13 @@ func (p *ShTokenizer) shAtomDquotBacktSq
 // shAtomInternal reads the next shtText or shtShVarUse.
 //
 // Examples:
-//  while
-//  text$$,text
-//  $$!
-//  $$$$
-//  text
-//  ${var:=default}
+//
+//     while
+//     text$$,text
+//     $$!
+//     $$$$
+//     text
+//     ${var:=default}
 func (p *ShTokenizer) shAtomInternal(q ShQuoting, dquot, squot bool) *ShAtom {
        if shVarUse := p.shVarUse(q); shVarUse != nil {
                p.inWord = true

Index: pkgsrc/pkgtools/pkglint/files/shtypes.go
diff -u pkgsrc/pkgtools/pkglint/files/shtypes.go:1.17 pkgsrc/pkgtools/pkglint/files/shtypes.go:1.18
--- pkgsrc/pkgtools/pkglint/files/shtypes.go:1.17       Sun Jun 30 20:56:19 2019
+++ pkgsrc/pkgtools/pkglint/files/shtypes.go    Sun Jan 29 13:36:31 2023
@@ -122,9 +122,10 @@ func (q ShQuoting) ToVarUseContext() Vuc
 // ShToken is an operator or a keyword or some text intermingled with variables.
 //
 // Examples:
-//  ;
-//  then
-//  "The number of pkgsrc packages in ${PREFIX} is $$packages."
+//
+//     ;
+//     then
+//     "The number of pkgsrc packages in ${PREFIX} is $$packages."
 //
 // See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_10_02
 type ShToken struct {

Index: pkgsrc/pkgtools/pkglint/files/substcontext_test.go
diff -u pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.37 pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.38
--- pkgsrc/pkgtools/pkglint/files/substcontext_test.go:1.37     Sat Jun 20 07:00:44 2020
+++ pkgsrc/pkgtools/pkglint/files/substcontext_test.go  Sun Jan 29 13:36:31 2023
@@ -398,10 +398,11 @@ func (s *Suite) Test_SubstContext_varass
 // For example, in the following snippet from mail/dkim-milter/options.mk
 // revision 1.9, there is a comment, but that is not a rationale and also
 // not related to the SUBST_CLASS variable at all:
-//  ### IPv6 support.
-//  .if !empty(PKG_OPTIONS:Minet6)
-//  SUBST_SED.libs+=        -e 's|@INET6@||g'
-//  .endif
+//
+//     ### IPv6 support.
+//     .if !empty(PKG_OPTIONS:Minet6)
+//     SUBST_SED.libs+=        -e 's|@INET6@||g'
+//     .endif
 func (s *Suite) Test_SubstContext_varassignOutsideBlock__rationale(c *check.C) {
        t := s.Init(c)
 

Index: pkgsrc/pkgtools/pkglint/files/tools.go
diff -u pkgsrc/pkgtools/pkglint/files/tools.go:1.26 pkgsrc/pkgtools/pkglint/files/tools.go:1.27
--- pkgsrc/pkgtools/pkglint/files/tools.go:1.26 Sun Oct  2 14:39:37 2022
+++ pkgsrc/pkgtools/pkglint/files/tools.go      Sun Jan 29 13:36:31 2023
@@ -61,21 +61,21 @@ func (tool *Tool) String() string {
 //
 // Additionally, all allowed cases from UsableAtRunTime are allowed.
 //
-//  VAR:=   ${TOOL}           # Not allowed since bsd.prefs.mk is not
-//                            # included yet.
+//     VAR:=   ${TOOL}           # Not allowed since bsd.prefs.mk is not
+//                               # included yet.
 //
-//  .include "../../bsd.prefs.mk"
+//     .include "../../bsd.prefs.mk"
 //
-//  VAR:=   ${TOOL}           # Allowed.
-//  VAR!=   ${TOOL}           # Allowed.
+//     VAR:=   ${TOOL}           # Allowed.
+//     VAR!=   ${TOOL}           # Allowed.
 //
-//  VAR=    ${${TOOL}:sh}     # Allowed; the :sh modifier is evaluated
-//                            # lazily, but when VAR should ever be
-//                            # evaluated at load time, this still means
-//                            # load time.
+//     VAR=    ${${TOOL}:sh}     # Allowed; the :sh modifier is evaluated
+//                               # lazily, but when VAR should ever be
+//                               # evaluated at load time, this still means
+//                               # load time.
 //
-//  .if ${TOOL:T} == "tool"   # Allowed.
-//  .endif
+//     .if ${TOOL:T} == "tool"   # Allowed.
+//     .endif
 func (tool *Tool) UsableAtLoadTime(seenPrefs bool) bool {
        return seenPrefs && tool.Validity == AfterPrefsMk
 }
@@ -84,23 +84,23 @@ func (tool *Tool) UsableAtLoadTime(seenP
 // in all {pre,do,post}-* targets, and by its variable name in all
 // runtime contexts.
 //
-//  VAR:=   ${TOOL}           # Not allowed; TOOL might not be initialized yet.
-//  VAR!=   ${TOOL}           # Not allowed; TOOL might not be initialized yet.
+//     VAR:=   ${TOOL}           # Not allowed; TOOL might not be initialized yet.
+//     VAR!=   ${TOOL}           # Not allowed; TOOL might not be initialized yet.
 //
-//  VAR=    ${${TOOL}:sh}     # Probably ok; the :sh modifier is evaluated at
-//                            # run time. But if VAR should ever be evaluated
-//                            # at load time (see the "Not allowed" cases
-//                            # above), it doesn't work. As of January 2019,
-//                            # pkglint cannot reliably distinguish these cases.
-//
-//  own-target:
-//          ${TOOL}           # Allowed.
-//          tool              # Not allowed because the PATH might not be set
-//                            # up for this target.
-//
-//  pre-configure:
-//          ${TOOL}           # Allowed.
-//          tool              # Allowed.
+//     VAR=    ${${TOOL}:sh}     # Probably ok; the :sh modifier is evaluated at
+//                               # run time. But if VAR should ever be evaluated
+//                               # at load time (see the "Not allowed" cases
+//                               # above), it doesn't work. As of January 2019,
+//                               # pkglint cannot reliably distinguish these cases.
+//
+//     own-target:
+//             ${TOOL}           # Allowed.
+//             tool              # Not allowed because the PATH might not be set
+//                               # up for this target.
+//
+//     pre-configure:
+//             ${TOOL}           # Allowed.
+//             tool              # Allowed.
 func (tool *Tool) UsableAtRunTime() bool {
        return tool.Validity == AtRunTime || tool.Validity == AfterPrefsMk
 }

Index: pkgsrc/pkgtools/pkglint/files/var.go
diff -u pkgsrc/pkgtools/pkglint/files/var.go:1.9 pkgsrc/pkgtools/pkglint/files/var.go:1.10
--- pkgsrc/pkgtools/pkglint/files/var.go:1.9    Fri Jun 12 19:14:45 2020
+++ pkgsrc/pkgtools/pkglint/files/var.go        Sun Jan 29 13:36:31 2023
@@ -66,9 +66,10 @@ func (v *Var) ConditionalVars() []string
 //
 // Variables that are used in .for loops in which this variable is assigned
 // a value, such as DIRS in:
-//  .for dir in ${DIRS}
-//  VAR+=${dir}
-//  .endfor
+//
+//     .for dir in ${DIRS}
+//     VAR+=${dir}
+//     .endfor
 func (v *Var) Refs() []string {
        return v.refs.Elements
 }
@@ -91,8 +92,9 @@ func (v *Var) AddRef(varname string) {
 // cases may be implemented later.
 //
 // TODO: Simple .for loops that append to the variable are ok as well.
-//  (This needs to be worded more precisely since that part potentially
-//  adds a lot of complexity to the whole data structure.)
+//
+//     (This needs to be worded more precisely since that part potentially
+//     adds a lot of complexity to the whole data structure.)
 //
 // Variable assignments in the pkgsrc infrastructure are taken into account
 // for determining whether a variable is constant.

Index: pkgsrc/pkgtools/pkglint/files/varalignblock.go
diff -u pkgsrc/pkgtools/pkglint/files/varalignblock.go:1.20 pkgsrc/pkgtools/pkglint/files/varalignblock.go:1.21
--- pkgsrc/pkgtools/pkglint/files/varalignblock.go:1.20 Sun Oct  2 14:39:37 2022
+++ pkgsrc/pkgtools/pkglint/files/varalignblock.go      Sun Jan 29 13:36:31 2023
@@ -20,8 +20,8 @@ import (
 // There are two types of continuation lines. The first type has just
 // the continuation backslash in the first line:
 //
-//  MULTI_LINE= \
-//          The value starts in the second line.
+//     MULTI_LINE= \
+//             The value starts in the second line.
 //
 // The backslash in the first line is usually aligned to the other variables
 // in the same paragraph. If the variable name is longer than the indentation
@@ -38,20 +38,20 @@ import (
 // indentation is not a single tab, it must match the indentation of the
 // other lines in the paragraph.
 //
-//  MULTI_LINE=     The value starts in the first line \
-//                  and continues in the second line.
+//     MULTI_LINE=     The value starts in the first line \
+//                     and continues in the second line.
 //
 // In lists or plain text, like in the example above, all values are
 // aligned in the same column. Some variables also contain code, and in
 // these variables, the line containing the first word defines how deep
 // the follow-up lines must be indented at least.
 //
-//  SHELL_CMD=                                                              \
-//          if ${PKG_ADMIN} pmatch ${PKGNAME} ${dependency}; then           \
-//                  ${ECHO} yes;                                            \
-//          else                                                            \
-//                  ${ECHO} no;                                             \
-//          fi
+//     SHELL_CMD=                                                              \
+//             if ${PKG_ADMIN} pmatch ${PKGNAME} ${dependency}; then           \
+//                     ${ECHO} yes;                                            \
+//             else                                                            \
+//                     ${ECHO} no;                                             \
+//             fi
 //
 // In the continuation lines, each follow-up line is indented with at least
 // one tab, to avoid confusing them with regular single-lines. This is
@@ -72,74 +72,74 @@ import (
 // Needs some more time to mature.
 // After implementing it, it will be translated into English.
 //
-//  Ebenen: Datei > Absatz > MkZeile > Zeile
+//     Ebenen: Datei > Absatz > MkZeile > Zeile
 //
-//  ### Datei
+//     ### Datei
 //
-//  #.  Ein einzelner Absatz, der einen Tab weniger eingerückt ist als die übrigen,
-//      darf auf die Einrückung der anderen Absätze angeglichen werden,
-//      sofern der Absatz dadurch nicht zu breit wird.
+//     #.  Ein einzelner Absatz, der einen Tab weniger eingerückt ist als die übrigen,
+//         darf auf die Einrückung der anderen Absätze angeglichen werden,
+//         sofern der Absatz dadurch nicht zu breit wird.
 //
-//  ### Einzelner Absatz
+//     ### Einzelner Absatz
 //
-//  #.  Jede Zeile besteht aus #, VarOp, Leerraum, Wert, Leerraum und Fortsetzung.
+//     #.  Jede Zeile besteht aus #, VarOp, Leerraum, Wert, Leerraum und Fortsetzung.
 //
-//  #.  Die Werte aller Zeilen sind mit Tabs an einer gemeinsamen vertikalen Linie
-//      (Ausrichtung) ausgerichtet.
+//     #.  Die Werte aller Zeilen sind mit Tabs an einer gemeinsamen vertikalen Linie
+//         (Ausrichtung) ausgerichtet.
 //
-//  #.  Das Ausrichten mit mehr als 1 Tab ist erlaubt, wenn die Ausrichtung einheitlich ist.
+//     #.  Das Ausrichten mit mehr als 1 Tab ist erlaubt, wenn die Ausrichtung einheitlich ist.
 //
-//  #.  Wenn VarOp über die Ausrichtung hinausragt (Ausreißer),
-//      darf zwischen VarOp und Wert statt der Ausrichtung 1 Leerzeichen sein.
+//     #.  Wenn VarOp über die Ausrichtung hinausragt (Ausreißer),
+//         darf zwischen VarOp und Wert statt der Ausrichtung 1 Leerzeichen sein.
 //
-//  #.  Die minimale Ausrichtung ergibt sich aus der maximalen Breite von # und VarOp
-//      aller Zeilen, gerundet zum nächsten Tabstopp.
-//      Dabei zählen auch Zeilen mit, die rechts von VarOp komplett leer sind.
+//     #.  Die minimale Ausrichtung ergibt sich aus der maximalen Breite von # und VarOp
+//         aller Zeilen, gerundet zum nächsten Tabstopp.
+//         Dabei zählen auch Zeilen mit, die rechts von VarOp komplett leer sind.
 //
-//  #.  Die maximale Ausrichtung ergibt sich aus der maximalen Breite von Wert
-//      und Kommentar, abgezogen vom maximalen rechten Rand (in Spalte 73).
+//     #.  Die maximale Ausrichtung ergibt sich aus der maximalen Breite von Wert
+//         und Kommentar, abgezogen vom maximalen rechten Rand (in Spalte 73).
 //
-//  #.  Beim Umformatieren darf die Zeilenbreite die 73 Zeichen nicht überschreiten,
-//      damit am rechten Rand eindeutig ist, wo jede Zeile aufhört.
-//      Zeilen, die bereits vorher breiter waren, dürfen ruhig noch breiter werden.
+//     #.  Beim Umformatieren darf die Zeilenbreite die 73 Zeichen nicht überschreiten,
+//         damit am rechten Rand eindeutig ist, wo jede Zeile aufhört.
+//         Zeilen, die bereits vorher breiter waren, dürfen ruhig noch breiter werden.
 //
-//  #.  Das Verhältnis zwischen Tab-Zeilen und hinausragenden Zeilen muss ausgewogen sein.
-//      Nicht zu viele hinausragende Zeilen. (Noch zu definieren.)
-//      Möglicher Ansatz: Anteil der Leerfläche?
+//     #.  Das Verhältnis zwischen Tab-Zeilen und hinausragenden Zeilen muss ausgewogen sein.
+//         Nicht zu viele hinausragende Zeilen. (Noch zu definieren.)
+//         Möglicher Ansatz: Anteil der Leerfläche?
 //
-//  ### Mehrzeilig
+//     ### Mehrzeilig
 //
-//  #.  Jede MkZeile hat für alle ihre Zeilen einen gemeinsamen rechten Rand.
+//     #.  Jede MkZeile hat für alle ihre Zeilen einen gemeinsamen rechten Rand.
 //
-//  #.  Die Fortsetzungen jeder MkZeile sind entweder alle durch je 1 Leerzeichen abgetrennt,
-//      oder alle Fortsetzungen sind am rechten Rand.
+//     #.  Die Fortsetzungen jeder MkZeile sind entweder alle durch je 1 Leerzeichen abgetrennt,
+//         oder alle Fortsetzungen sind am rechten Rand.
 //
-//  #.  Um den gemeinsamen rechten Rand zu bestimmen, werden alle Zeilen ignoriert,
-//      in denen die Fortsetzung durch 1 Leerzeichen abgetrennt ist.
+//     #.  Um den gemeinsamen rechten Rand zu bestimmen, werden alle Zeilen ignoriert,
+//         in denen die Fortsetzung durch 1 Leerzeichen abgetrennt ist.
 //
-//  #.  Einzelne Fortsetzungen dürfen über den rechten Rand hinausragen.
-//      Die Fortsetzung wird dann durch 1 Leerzeichen abgetrennt.
+//     #.  Einzelne Fortsetzungen dürfen über den rechten Rand hinausragen.
+//         Die Fortsetzung wird dann durch 1 Leerzeichen abgetrennt.
 //
-//  ### Mehrzeilig, Erstzeile
+//     ### Mehrzeilig, Erstzeile
 //
-//  #.  Die Fortsetzung der Erstzeile ist durch 1 Leerzeichen abgetrennt,
-//      wenn sie rechts von der Ausrichtung steht,
-//      andernfalls durch Tabs an der Ausrichtung.
+//     #.  Die Fortsetzung der Erstzeile ist durch 1 Leerzeichen abgetrennt,
+//         wenn sie rechts von der Ausrichtung steht,
+//         andernfalls durch Tabs an der Ausrichtung.
 //
-//  #.  Eine leere Erstzeile mit 1 fortgesetzer Zeile ist nur zulässig,
-//      wenn die kombinierte Zeile breiter als 73 Zeichen wäre.
-//      Sonst werden die beiden Zeilen kombiniert.
+//     #.  Eine leere Erstzeile mit 1 fortgesetzer Zeile ist nur zulässig,
+//         wenn die kombinierte Zeile breiter als 73 Zeichen wäre.
+//         Sonst werden die beiden Zeilen kombiniert.
 //
-//  ### Mehrzeilig, fortgesetzte Zeilen
+//     ### Mehrzeilig, fortgesetzte Zeilen
 //
-//  #.  Nach einer leeren Erstzeile ist die erste fortgesetzte Zeile an der
-//      Ausrichtung aller Zeilen eingerückt, wenn die Erstzeile über die
-//      Ausrichtung ragt und der Platz aller Zeilen es zulässt, andernfalls
-//      mit 1 Tab.
+//     #.  Nach einer leeren Erstzeile ist die erste fortgesetzte Zeile an der
+//         Ausrichtung aller Zeilen eingerückt, wenn die Erstzeile über die
+//         Ausrichtung ragt und der Platz aller Zeilen es zulässt, andernfalls
+//         mit 1 Tab.
 //
-//  #.  Bei mehrzeiligen einrückbaren Werten (AWK, Shell, Listen aus Tupeln)
-//      dürfen die weiteren Fortsetzungszeilen weiter eingerückt sein als die erste.
-//      Ihre Einrückung besteht aus Tabs, gefolgt von 0 bis 7 Leerzeichen.
+//     #.  Bei mehrzeiligen einrückbaren Werten (AWK, Shell, Listen aus Tupeln)
+//         dürfen die weiteren Fortsetzungszeilen weiter eingerückt sein als die erste.
+//         Ihre Einrückung besteht aus Tabs, gefolgt von 0 bis 7 Leerzeichen.
 type VaralignBlock struct {
        mkinfos []*varalignMkLine
        skip    bool
@@ -455,9 +455,11 @@ type varalignMkLine struct {
 //
 // The follow-up lines of these lines may be indented with as few
 // as a single tab. Example:
-//  VAR= \
-//          value1 \
-//          value2
+//
+//     VAR= \
+//             value1 \
+//             value2
+//
 // In all other lines, the indentation must be at least the indentation
 // of the first value found.
 func (l *varalignMkLine) isMultiEmpty() bool {
@@ -740,7 +742,9 @@ func (p *varalignParts) varnameOpIndex()
 
 // spaceBeforeValueIndex returns the string index at which the space before the value starts.
 // It's the same as the end of the assignment operator. Example:
-//  #VAR=   value
+//
+//     #VAR=   value
+//
 // The index is 5.
 func (p *varalignParts) spaceBeforeValueIndex() int {
        return p.varnameOpIndex() + len(p.varnameOp)

Index: pkgsrc/pkgtools/pkglint/files/varalignblock_test.go
diff -u pkgsrc/pkgtools/pkglint/files/varalignblock_test.go:1.18 pkgsrc/pkgtools/pkglint/files/varalignblock_test.go:1.19
--- pkgsrc/pkgtools/pkglint/files/varalignblock_test.go:1.18    Sat Jan  1 12:44:25 2022
+++ pkgsrc/pkgtools/pkglint/files/varalignblock_test.go Sun Jan 29 13:36:31 2023
@@ -1937,8 +1937,9 @@ func (s *Suite) Test_VaralignBlock__var_
 // to be adjusted to the minimum required.
 //
 // FIXME: The definition of an outlier should be based on the actual indentation,
-//  not on the minimum indentation. Or maybe even better on the corrected indentation.
-//  In the below paragraph, the outlier is not indented enough to qualify as a visual outlier.
+//
+//     not on the minimum indentation. Or maybe even better on the corrected indentation.
+//     In the below paragraph, the outlier is not indented enough to qualify as a visual outlier.
 func (s *Suite) Test_VaralignBlock__var_tab32_value_var_tabs24_value_var_tabs24_value(c *check.C) {
        vt := NewVaralignTester(s, c)
        vt.Input(
@@ -1962,7 +1963,8 @@ func (s *Suite) Test_VaralignBlock__var_
 // initial line, this is intentional.
 //
 // TODO: Make this rule more general: if the indentation of the continuation
-//  lines is more than the initial line, it is intentional.
+//
+//     lines is more than the initial line, it is intentional.
 func (s *Suite) Test_VaralignBlock__var_tab24_value_space_cont_tabs32_value_space_cont_tabs32_value(c *check.C) {
        vt := NewVaralignTester(s, c)
        vt.Input(

Index: pkgsrc/pkgtools/pkglint/files/vartype.go
diff -u pkgsrc/pkgtools/pkglint/files/vartype.go:1.54 pkgsrc/pkgtools/pkglint/files/vartype.go:1.55
--- pkgsrc/pkgtools/pkglint/files/vartype.go:1.54       Sat Jan  1 12:44:25 2022
+++ pkgsrc/pkgtools/pkglint/files/vartype.go    Sun Jan 29 13:36:31 2023
@@ -508,7 +508,8 @@ func init() {
 }
 
 // TODO: Move these values to VarTypeRegistry.Init and read them from the
-//  pkgsrc infrastructure files, as far as possible.
+//
+//     pkgsrc infrastructure files, as far as possible.
 const (
        // See mk/emulator/emulator-vars.mk.
        emulOpsysValues = "" +

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.102 pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.103
--- pkgsrc/pkgtools/pkglint/files/vartypecheck.go:1.102 Fri Aug 19 17:35:43 2022
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck.go       Sun Jan 29 13:36:31 2023
@@ -35,24 +35,24 @@ func (cv *VartypeCheck) Explain(explanat
 //
 // Usage:
 //
-//  fix := cv.Autofix()
+//     fix := cv.Autofix()
 //
-//  fix.Errorf("Must not be ...")
-//  fix.Warnf("Should not be ...")
-//  fix.Notef("It is also possible ...")
-//
-//  fix.Explain(
-//      "Explanation ...",
-//      "... end of explanation.")
-//
-//  fix.Replace("from", "to")
-//  fix.ReplaceAfter("prefix", "from", "to")
-//  fix.InsertAbove("new line")
-//  fix.InsertBelow("new line")
-//  fix.Delete()
-//  fix.Custom(func(showAutofix, autofix bool) {})
+//     fix.Errorf("Must not be ...")
+//     fix.Warnf("Should not be ...")
+//     fix.Notef("It is also possible ...")
+//
+//     fix.Explain(
+//         "Explanation ...",
+//         "... end of explanation.")
+//
+//     fix.Replace("from", "to")
+//     fix.ReplaceAfter("prefix", "from", "to")
+//     fix.InsertAbove("new line")
+//     fix.InsertBelow("new line")
+//     fix.Delete()
+//     fix.Custom(func(showAutofix, autofix bool) {})
 //
-//  fix.Apply()
+//     fix.Apply()
 func (cv *VartypeCheck) Autofix() *Autofix { return cv.MkLine.Autofix() }
 
 // WithValue returns a new VartypeCheck context by copying all
@@ -763,9 +763,10 @@ func (cv *VartypeCheck) LdFlag() {
                return
        }
 
+       // See MkLineChecker.checkTextRpath.
        ldflag := cv.Value
-       if m, rpathFlag := match1(ldflag, `^(-Wl,(?:-R|-rpath|--rpath))`); m {
-               cv.Warnf("Please use \"${COMPILER_RPATH_FLAG}\" instead of %q.", rpathFlag)
+       if m, rpathFlag := match1(ldflag, `^(-Wl,--rpath,|-Wl,-rpath-link,|-Wl,-rpath,|-Wl,-R\b)`); m {
+               cv.Warnf("Please use ${COMPILER_RPATH_FLAG} instead of %q.", rpathFlag)
                return
        }
 
@@ -1457,9 +1458,9 @@ func (cv *VartypeCheck) ToolName() {
 // Unknown doesn't check for anything.
 //
 // Used for:
-//  - infrastructure variables that are not in vardefs.go
-//  - other variables whose type is unknown or not interesting enough to
-//    warrant the creation of a specialized type
+//   - infrastructure variables that are not in vardefs.go
+//   - other variables whose type is unknown or not interesting enough to
+//     warrant the creation of a specialized type
 func (cv *VartypeCheck) Unknown() {
        // Do nothing.
 }

Index: pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go
diff -u pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.94 pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.95
--- pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go:1.94     Fri Aug 19 17:35:43 2022
+++ pkgsrc/pkgtools/pkglint/files/vartypecheck_test.go  Sun Jan 29 13:36:31 2023
@@ -1301,7 +1301,7 @@ func (s *Suite) Test_VartypeCheck_LdFlag
                "anything")
 
        vt.Output(
-               "WARN: filename.mk:6: Please use \"${COMPILER_RPATH_FLAG}\" instead of \"-Wl,--rpath\".",
+               "WARN: filename.mk:6: Please use ${COMPILER_RPATH_FLAG} instead of \"-Wl,--rpath,\".",
                "WARN: filename.mk:13: \"-DMACRO\" is a compiler flag "+
                        "and belongs on CFLAGS, CPPFLAGS, CXXFLAGS or FFLAGS instead of LDFLAGS.",
                "WARN: filename.mk:14: \"-UMACRO\" is a compiler flag "+

Index: pkgsrc/pkgtools/pkglint/files/getopt/getopt.go
diff -u pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.9 pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.10
--- pkgsrc/pkgtools/pkglint/files/getopt/getopt.go:1.9  Sun Jan 26 17:12:37 2020
+++ pkgsrc/pkgtools/pkglint/files/getopt/getopt.go      Sun Jan 29 13:36:32 2023
@@ -22,11 +22,12 @@ func NewOptions() *Options {
 // AddFlagGroup adds an option that takes multiple flag values.
 //
 // Example:
-//  var extra bool
 //
-//  opts := NewOptions()
-//  warnings := opts.AddFlagGroup('W', "warnings", "warning,...", "Enable the given warnings")
-//  warnings.AddFlagVar("extra", &extra, false, "Print extra warnings")
+//     var extra bool
+//
+//     opts := NewOptions()
+//     warnings := opts.AddFlagGroup('W', "warnings", "warning,...", "Enable the given warnings")
+//     warnings.AddFlagVar("extra", &extra, false, "Print extra warnings")
 func (o *Options) AddFlagGroup(shortName rune, longName, argsName, description string) *FlagGroup {
        grp := new(FlagGroup)
        opt := option{shortName, longName, argsName, description, grp}
@@ -37,16 +38,18 @@ func (o *Options) AddFlagGroup(shortName
 // AddFlagVar adds a boolean flag to the options.
 //
 // Example:
-//  var verbose bool
 //
-//  opts := NewOptions()
-//  opts.AddFlagVar('v', "verbose", &verbose, false, "Enable verbose output")
+//     var verbose bool
+//
+//     opts := NewOptions()
+//     opts.AddFlagVar('v', "verbose", &verbose, false, "Enable verbose output")
 //
 // This option can be used in the following ways:
-//  -v
-//  --verbose
-//  --verbose=on    (or yes, 1, true, enabled)
-//  --verbose=off   (or no, 0, false, disabled)
+//
+//     -v
+//     --verbose
+//     --verbose=on    (or yes, 1, true, enabled)
+//     --verbose=off   (or no, 0, false, disabled)
 func (o *Options) AddFlagVar(shortName rune, longName string, pflag *bool, defval bool, description string) {
        *pflag = defval
        opt := option{shortName, longName, "", description, pflag}
@@ -56,15 +59,17 @@ func (o *Options) AddFlagVar(shortName r
 // AddStrVar adds a string option to the options.
 //
 // Example:
-//  var outputFilename string
 //
-//  opts := NewOptions()
-//  opts.AddStrVar('o', "output", &outputFilename, "", "Write the output to the given file")
+//     var outputFilename string
+//
+//     opts := NewOptions()
+//     opts.AddStrVar('o', "output", &outputFilename, "", "Write the output to the given file")
 //
 // This option can be used in the following ways:
-//  -o output.txt
-//  --output output.txt
-//  --output=output.txt
+//
+//     -o output.txt
+//     --output output.txt
+//     --output=output.txt
 func (o *Options) AddStrVar(shortName rune, longName string, pstr *string, defval string, description string) {
        *pstr = defval
        opt := option{shortName, longName, "", description, pstr}
@@ -74,15 +79,17 @@ func (o *Options) AddStrVar(shortName ru
 // AddStrList adds a string option to the options that can be used multiple times.
 //
 // Example:
-//  var includes []string
 //
-//  opts := NewOptions()
-//  opts.AddStrList('i', "include", &includes, nil, "Include the files matching the pattern")
+//     var includes []string
+//
+//     opts := NewOptions()
+//     opts.AddStrList('i', "include", &includes, nil, "Include the files matching the pattern")
 //
 // This option can be used in the following ways:
-//  -i "*.txt" -i "*.docx"
-//  --include "*.txt" --include "*.md"
-//  --include="*.txt" --include="*.md"
+//
+//     -i "*.txt" -i "*.docx"
+//     --include "*.txt" --include "*.md"
+//     --include="*.txt" --include="*.md"
 func (o *Options) AddStrList(shortName rune, longName string, plist *[]string, description string) {
        *plist = []string{}
        opt := option{shortName, longName, "", description, plist}

Index: pkgsrc/pkgtools/pkglint/files/intqa/qa.go
diff -u pkgsrc/pkgtools/pkglint/files/intqa/qa.go:1.5 pkgsrc/pkgtools/pkglint/files/intqa/qa.go:1.6
--- pkgsrc/pkgtools/pkglint/files/intqa/qa.go:1.5       Fri Jun 25 14:15:01 2021
+++ pkgsrc/pkgtools/pkglint/files/intqa/qa.go   Sun Jan 29 13:36:32 2023
@@ -48,7 +48,9 @@ const (
 // QAChecker analyzes Go source code for consistency.
 //
 // Among others, it enforces a strict naming scheme for test methods:
-//  Test_${Type}_${Method}__${description_using_underscores}
+//
+//     Test_${Type}_${Method}__${description_using_underscores}
+//
 // Each of the variable parts may be omitted.
 type QAChecker struct {
        errorf func(format string, args ...interface{})

Index: pkgsrc/pkgtools/pkglint/files/makepat/pat.go
diff -u pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.3 pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.4
--- pkgsrc/pkgtools/pkglint/files/makepat/pat.go:1.3    Sun Aug  8 22:04:15 2021
+++ pkgsrc/pkgtools/pkglint/files/makepat/pat.go        Sun Jan 29 13:36:32 2023
@@ -317,8 +317,9 @@ func (p *Pattern) compressed(relevant []
 // CanMatch tests whether the pattern can match some string.
 // Most patterns can do that.
 // Typical counterexamples are:
-//  [^]
-//  Intersect("*.c", "*.h")
+//
+//     [^]
+//     Intersect("*.c", "*.h")
 func (p *Pattern) CanMatch() bool {
        if len(p.states) == 0 {
                return false

Index: pkgsrc/pkgtools/pkglint/files/trace/tracing.go
diff -u pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.11 pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.12
--- pkgsrc/pkgtools/pkglint/files/trace/tracing.go:1.11 Sat Nov 23 23:35:56 2019
+++ pkgsrc/pkgtools/pkglint/files/trace/tracing.go      Sun Jan 29 13:36:32 2023
@@ -40,9 +40,10 @@ func (t *Tracer) Step2(format string, ar
 // Call0 is used to trace a no-arguments function call.
 //
 // Usage:
-//  if trace.Tracing {
-//      defer trace.Call0()()
-//  }
+//
+//     if trace.Tracing {
+//         defer trace.Call0()()
+//     }
 func (t *Tracer) Call0() func() {
        return t.traceCall()
 }
@@ -50,9 +51,10 @@ func (t *Tracer) Call0() func() {
 // Call1 is used to trace a function call with a single string argument.
 //
 // Usage:
-//  if trace.Tracing {
-//      defer trace.Call1(str1)()
-//  }
+//
+//     if trace.Tracing {
+//         defer trace.Call1(str1)()
+//     }
 func (t *Tracer) Call1(arg1 string) func() {
        return t.traceCall(arg1)
 }
@@ -60,9 +62,10 @@ func (t *Tracer) Call1(arg1 string) func
 // Call2 is used to trace a function call with 2 string arguments.
 //
 // Usage:
-//  if trace.Tracing {
-//      defer trace.Call2(str1, str2)()
-//  }
+//
+//     if trace.Tracing {
+//         defer trace.Call2(str1, str2)()
+//     }
 func (t *Tracer) Call2(arg1, arg2 string) func() {
        return t.traceCall(arg1, arg2)
 }
@@ -71,9 +74,10 @@ func (t *Tracer) Call2(arg1, arg2 string
 // when leaving the function.
 //
 // Usage:
-//  if trace.Tracing {
-//      defer trace.Call(arg1, arg2, trace.Result(result1), trace.Result(result2))()
-// }
+//
+//      if trace.Tracing {
+//          defer trace.Call(arg1, arg2, trace.Result(result1), trace.Result(result2))()
+//     }
 func (t *Tracer) Call(args ...interface{}) func() {
        return t.traceCall(args...)
 }

Added files:

Index: pkgsrc/pkgtools/pkglint/files/changes.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/changes.go:1.1
--- /dev/null   Sun Jan 29 13:36:33 2023
+++ pkgsrc/pkgtools/pkglint/files/changes.go    Sun Jan 29 13:36:31 2023
@@ -0,0 +1,397 @@
+package pkglint
+
+import (
+       "netbsd.org/pkglint/pkgver"
+       "netbsd.org/pkglint/textproc"
+       "sort"
+       "strings"
+)
+
+// Changes collects the doc/CHANGES-* files, which mainly contain package
+// updates, as well as other package modifications and changes to the pkgsrc
+// infrastructure.
+type Changes struct {
+       LastChange      map[PkgsrcPath]*Change
+       LastFreezeStart string // e.g. "2018-01-01", or ""
+       LastFreezeEnd   string // e.g. "2018-01-01", or ""
+}
+
+func (ch *Changes) load(src *Pkgsrc) {
+       docDir := src.File("doc")
+       files := src.ReadDir("doc")
+       if len(files) == 0 {
+               G.Logger.TechFatalf(docDir, "Cannot be read for loading the package changes.")
+       }
+
+       var filenames []RelPath
+       for _, file := range files {
+               filename := file.Name()
+               // Files before 2011 are too far in the past to be still relevant today.
+               if matches(filename, `^CHANGES-20\d\d$`) && filename >= "CHANGES-2011" {
+                       filenames = append(filenames, NewRelPathString(filename))
+               }
+       }
+
+       ch.LastChange = make(map[PkgsrcPath]*Change)
+       for _, filename := range filenames {
+               changes := ch.parseFile(docDir.JoinNoClean(filename), false)
+               for _, change := range changes {
+                       ch.LastChange[change.Pkgpath] = change
+                       if change.Action == Renamed || change.Action == Moved {
+                               ch.LastChange[change.Target()] = change
+                       }
+               }
+       }
+
+       ch.checkRemovedAfterLastFreeze(src)
+}
+
+func (ch *Changes) parseFile(filename CurrPath, direct bool) []*Change {
+
+       warn := direct || G.CheckGlobal && !G.Wip
+
+       // Each date in the file should be from the same year as the filename says.
+       // This check has been added in 2018.
+       // For years earlier than 2018 pkglint doesn't care because it's not a big issue anyway.
+       year := ""
+       if _, yyyy := match1(filename.Base().String(), `-(\d\d\d\d)$`); yyyy >= "2018" {
+               year = yyyy
+       }
+       thorough := direct || G.CheckGlobal || year >= "2020" // For performance reasons
+
+       latest := make(map[PkgsrcPath]*Change)
+
+       infra := false
+       lines := Load(filename, MustSucceed|NotEmpty)
+       var changes []*Change
+       for _, line := range lines.Lines {
+
+               if hasPrefix(line.Text, "\tmk/") {
+                       infra = true
+                       if hasPrefix(line.Text, "\tmk/bsd.pkg.mk: started freeze for") {
+                               if m, date := match1(line.Text, `(\d\d\d\d-\d\d-\d\d)\]$`); m {
+                                       ch.LastFreezeStart = date
+                                       ch.LastFreezeEnd = ""
+                               }
+                       } else if hasPrefix(line.Text, "\tmk/bsd.pkg.mk: freeze ended for") {
+                               if m, date := match1(line.Text, `(\d\d\d\d-\d\d-\d\d)\]$`); m {
+                                       ch.LastFreezeEnd = date
+                               }
+                       }
+               }
+               if infra {
+                       if hasSuffix(line.Text, "]") {
+                               infra = false
+                       }
+                       continue
+               }
+
+               change := ch.parseLine(line, warn)
+               if change == nil {
+                       continue
+               }
+
+               changes = append(changes, change)
+
+               if !warn {
+                       continue
+               }
+
+               if thorough {
+                       ch.checkChangeVersion(change, latest, line)
+                       ch.checkChangeDate(filename, year, change, line, changes)
+               }
+       }
+
+       return changes
+}
+
+func (*Changes) parseLine(line *Line, warn bool) *Change {
+       lex := textproc.NewLexer(line.Text)
+
+       space := lex.NextHspace()
+       if space == "" {
+               return nil
+       }
+
+       if space != "\t" {
+               if warn {
+                       line.Warnf("Package changes should be indented using a single tab, not %q.", space)
+                       line.Explain(
+                               "To avoid this formatting mistake in the future, just run",
+                               sprintf("%q", bmake("cce")),
+                               "after committing the update to the package.")
+               }
+
+               return nil
+       }
+
+       invalid := func() *Change {
+               if warn {
+                       line.Warnf("Invalid doc/CHANGES line: %s", line.Text)
+                       line.Explain(
+                               "See mk/misc/developer.mk for the rules.",
+                               "",
+                               "To generate these entries automatically, run",
+                               sprintf("%q.", bmakeHelp("cce")))
+               }
+               return nil
+       }
+
+       f := strings.Fields(lex.Rest())
+       n := len(f)
+       if n > 1 && hasSuffix(f[0], ":") {
+               return nil
+       }
+       if n == 0 {
+               return invalid()
+       }
+
+       action := ParseChangeAction(f[0])
+       var pkgpath, author, date string
+       if n > 1 {
+               pkgpath = f[1]
+               date = f[n-1]
+       }
+       if n > 2 {
+               author = f[n-2]
+       }
+
+       author, date = (*Changes).parseAuthorAndDate(nil, author, date)
+       if author == "" {
+               return invalid()
+       }
+
+       switch {
+       case
+               action == Added && f[2] == "version" && n == 6,
+               action == Updated && f[2] == "to" && n == 6,
+               action == Downgraded && f[2] == "to" && n == 6,
+               action == Removed && ((f[2] == "successor" || f[2] == "version") && n == 6 || n == 4),
+               (action == Renamed || action == Moved) && f[2] == "to" && n == 6:
+               return &Change{
+                       Location: line.Location,
+                       Action:   action,
+                       Pkgpath:  NewPkgsrcPath(NewPath(intern(pkgpath))),
+                       target:   intern(condStr(n == 6, f[3], "")),
+                       Author:   intern(author),
+                       Date:     intern(date),
+               }
+       }
+
+       return invalid()
+}
+
+// parseAuthorAndDate parses the author and date from a line in doc/CHANGES.
+func (*Changes) parseAuthorAndDate(author, date string) (string, string) {
+       alex := textproc.NewLexer(author)
+       if !alex.SkipByte('[') {
+               return "", ""
+       }
+       author = alex.NextBytesSet(textproc.AlnumU)
+       if !alex.EOF() {
+               return "", ""
+       }
+
+       isDigit := func(b byte) bool { return '0' <= b && b <= '9' }
+
+       if len(date) == 11 &&
+               isDigit(date[0]) &&
+               isDigit(date[1]) &&
+               isDigit(date[2]) &&
+               isDigit(date[3]) &&
+               date[4] == '-' &&
+               isDigit(date[5]) &&
+               isDigit(date[6]) &&
+               10*(date[5]-'0')+(date[6]-'0') >= 1 &&
+               10*(date[5]-'0')+(date[6]-'0') <= 12 &&
+               date[7] == '-' &&
+               isDigit(date[8]) &&
+               isDigit(date[9]) &&
+               10*(date[8]-'0')+(date[9]-'0') >= 1 &&
+               10*(date[8]-'0')+(date[9]-'0') <= 31 &&
+               date[10] == ']' {
+               date = date[:10]
+               return author, date
+       }
+
+       return "", ""
+}
+
+func (ch *Changes) checkChangeVersion(change *Change, latest map[PkgsrcPath]*Change, line *Line) {
+       switch change.Action {
+
+       case Added:
+               ch.checkChangeVersionNumber(change, line)
+               existing := latest[change.Pkgpath]
+               if existing != nil && existing.Version() == change.Version() {
+                       line.Warnf("Package %q was already added in %s.",
+                               change.Pkgpath.String(), line.RelLocation(existing.Location))
+               }
+               latest[change.Pkgpath] = change
+
+       case Updated:
+               ch.checkChangeVersionNumber(change, line)
+               existing := latest[change.Pkgpath]
+               if existing != nil && pkgver.Compare(change.Version(), existing.Version()) <= 0 {
+                       line.Warnf("Updating %q from %s in %s to %s should increase the version number.",
+                               change.Pkgpath.String(), existing.Version(), line.RelLocation(existing.Location), change.Version())
+               }
+               latest[change.Pkgpath] = change
+
+       case Downgraded:
+               ch.checkChangeVersionNumber(change, line)
+               existing := latest[change.Pkgpath]
+               if existing != nil && pkgver.Compare(change.Version(), existing.Version()) >= 0 {
+                       line.Warnf("Downgrading %q from %s in %s to %s should decrease the version number.",
+                               change.Pkgpath.String(), existing.Version(), line.RelLocation(existing.Location), change.Version())
+               }
+               latest[change.Pkgpath] = change
+
+       default:
+               latest[change.Pkgpath] = nil
+       }
+}
+
+func (*Changes) checkChangeVersionNumber(change *Change, line *Line) {
+       version := change.Version()
+
+       switch {
+       case !textproc.NewLexer(version).TestByteSet(textproc.Digit):
+               line.Warnf("Version number %q should start with a digit.", version)
+
+       // See rePkgname for the regular expression.
+       case !matches(version, `^([0-9][.\-0-9A-Z_a-z]*)$`):
+               line.Warnf("Malformed version number %q.", version)
+       }
+}
+
+func (*Changes) checkChangeDate(filename CurrPath, year string, change *Change, line *Line, changes []*Change) {
+       if year != "" && change.Date[0:4] != year {
+               line.Warnf("Year %q for %s does not match the filename %s.",
+                       change.Date[0:4], change.Pkgpath.String(), line.Rel(filename))
+       }
+
+       if len(changes) >= 2 && year != "" {
+               if prev := changes[len(changes)-2]; change.Date < prev.Date {
+                       line.Warnf("Date %q for %s is earlier than %q in %s.",
+                               change.Date, change.Pkgpath.String(), prev.Date, line.RelLocation(prev.Location))
+                       line.Explain(
+                               "The entries in doc/CHANGES should be in chronological order, and",
+                               "all dates are assumed to be in the UTC timezone, to prevent time",
+                               "warps.",
+                               "",
+                               "To fix this, determine which of the involved dates are correct",
+                               "and which aren't.",
+                               "",
+                               "To prevent this kind of mistakes in the future,",
+                               "make sure that your system time is correct and run",
+                               sprintf("%q", bmake("cce")),
+                               "to commit the changes entry.")
+               }
+       }
+}
+
+func (ch *Changes) checkRemovedAfterLastFreeze(src *Pkgsrc) {
+       if ch.LastFreezeStart == "" || G.Wip || !G.CheckGlobal {
+               return
+       }
+
+       var wrong []*Change
+       for pkgsrcPath, change := range ch.LastChange {
+               switch change.Action {
+               case Added, Updated, Downgraded:
+                       if !src.File(pkgsrcPath).IsDir() {
+                               wrong = append(wrong, change)
+                       }
+               }
+       }
+
+       sort.Slice(wrong, func(i, j int) bool { return wrong[i].IsAbove(wrong[j]) })
+
+       for _, change := range wrong {
+               // The original line of the change is not available anymore.
+               // Therefore, it is necessary to load the whole file again.
+               lines := Load(change.Location.Filename, MustSucceed)
+               line := lines.Lines[change.Location.lineno-1]
+               line.Errorf("Package %s must either exist or be marked as removed.", change.Pkgpath.String())
+       }
+}
+
+// Change describes a modification to a single package, from the doc/CHANGES-* files.
+type Change struct {
+       Location Location
+       Action   ChangeAction // Added, Updated, Downgraded, Renamed, Moved, Removed
+       Pkgpath  PkgsrcPath   // For renamed or moved packages, the previous PKGPATH
+       target   string       // The path or version number, depending on the action
+       Author   string
+       Date     string
+}
+
+// Version returns the version number for an Added, Updated or Downgraded package.
+func (ch *Change) Version() string {
+       assert(ch.Action == Added || ch.Action == Updated || ch.Action == Downgraded)
+       return ch.target
+}
+
+// Target returns the target PKGPATH for a Renamed or Moved package.
+func (ch *Change) Target() PkgsrcPath {
+       assert(ch.Action == Renamed || ch.Action == Moved)
+       return NewPkgsrcPath(NewPath(ch.target))
+}
+
+// SuccessorOrVersion returns the successor for a Removed package,
+// or the version number of its last appearance.
+// As of 2020-10-06, no cross-validation is done on this field though.
+func (ch *Change) SuccessorOrVersion() string {
+       assert(ch.Action == Removed)
+       return ch.target
+}
+
+func (ch *Change) IsAbove(other *Change) bool {
+       if ch.Date != other.Date {
+               return ch.Date < other.Date
+       }
+       return ch.Location.lineno < other.Location.lineno
+}
+
+type ChangeAction uint8
+
+const (
+       Added ChangeAction = 1 + iota
+       Updated
+       Downgraded
+       Renamed
+       Moved
+       Removed
+)
+
+func ParseChangeAction(s string) ChangeAction {
+       switch s {
+       case "Added":
+               return Added
+       case "Updated":
+               return Updated
+       case "Downgraded":
+               return Downgraded
+       case "Renamed":
+               return Renamed
+       case "Moved":
+               return Moved
+       case "Removed":
+               return Removed
+       }
+       return 0
+}
+
+func (ca ChangeAction) String() string {
+       return [...]string{"", "Added", "Updated", "Downgraded", "Renamed", "Moved", "Removed"}[ca]
+}
+
+// SuggestedUpdate describes a desired package update, from the doc/TODO file.
+type SuggestedUpdate struct {
+       Line    Location
+       Pkgname string
+       Version string
+       Comment string
+}
Index: pkgsrc/pkgtools/pkglint/files/changes_test.go
diff -u /dev/null pkgsrc/pkgtools/pkglint/files/changes_test.go:1.1
--- /dev/null   Sun Jan 29 13:36:33 2023
+++ pkgsrc/pkgtools/pkglint/files/changes_test.go       Sun Jan 29 13:36:31 2023
@@ -0,0 +1,640 @@
+package pkglint
+
+import (
+       "gopkg.in/check.v1"
+       "strings"
+)
+
+func (s *Suite) Test_Changes_load(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPkgsrc()
+       t.CreateFileLines("doc/CHANGES-2018",
+               CvsID,
+               "",
+               "\tUpdated pkgpath to 1.0 [author 2018-01-01]",
+               "\tRenamed pkgpath to new-pkg [author 2018-02-01]",
+               "\tMoved pkgpath to category/new-pkg [author 2018-03-01]")
+       t.FinishSetUp()
+
+       t.CheckEquals(G.Pkgsrc.changes.LastChange["pkgpath"].Action, Moved)
+}
+
+func (s *Suite) Test_Changes_load__not_found(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPkgsrc()
+       t.Remove("doc/CHANGES-2018")
+       t.Remove("doc/TODO")
+       t.Remove("doc")
+
+       t.ExpectFatal(
+               t.FinishSetUp,
+               "FATAL: ~/doc: Cannot be read for loading the package changes.")
+}
+
+func (s *Suite) Test_Changes_parseFile(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("doc/CHANGES-2018",
+               "\tAdded category/package version 1.0 [author1 2015-01-01]", // Wrong year
+               "\tUpdated category/package to 1.5 [author2 2018-01-02]",
+               "\tRenamed category/package to category/pkg [author3 2018-01-03]",
+               "\tMoved category/package to other/package [author4 2018-01-04]",
+               "\tRemoved category/package [author5 2018-01-09]", // Too far in the future
+               "\tRemoved category/package successor category/package2 [author6 2018-01-06]",
+               "\tDowngraded category/package to 1.2 [author7 2018-01-07]",
+               "\tReworked category/package to 1.2 [author8 2018-01-08]",
+               "",
+               "\ttoo few fields",
+               "\ttoo many many many many many fields",
+               "\tmissing brackets around author",
+               "\tAdded another [new package]",
+               "",
+               "\tmk/bsd.pkg.mk: freeze ended for branch pkgsrc-2019Q2", // missing date
+               "\tmk/bsd.pkg.mk: freeze ended for branch pkgsrc-2019Q2 [thawer 2019-07-01]",
+               "",
+               "Normal paragraph.")
+
+       changes := G.Pkgsrc.changes.parseFile(t.File("doc/CHANGES-2018"), true)
+
+       t.CheckDeepEquals(
+               changes, []*Change{
+                       {changes[0].Location,
+                               Added, "category/package", "1.0",
+                               "author1", "2015-01-01"},
+                       {changes[1].Location,
+                               Updated, "category/package", "1.5",
+                               "author2", "2018-01-02"},
+                       {changes[2].Location,
+                               Renamed, "category/package", "category/pkg",
+                               "author3", "2018-01-03"},
+                       {changes[3].Location,
+                               Moved, "category/package", "other/package",
+                               "author4", "2018-01-04"},
+                       {changes[4].Location,
+                               Removed, "category/package", "",
+                               "author5", "2018-01-09"},
+                       {changes[5].Location,
+                               Removed, "category/package", "category/package2",
+                               "author6", "2018-01-06"},
+                       {changes[6].Location,
+                               Downgraded, "category/package", "1.2",
+                               "author7", "2018-01-07"}})
+
+       t.CheckOutputLines(
+               "WARN: ~/doc/CHANGES-2018:1: Year \"2015\" for category/package does not match the filename CHANGES-2018.",
+               "WARN: ~/doc/CHANGES-2018:6: Date \"2018-01-06\" for category/package is earlier than \"2018-01-09\" in line 5.",
+               "WARN: ~/doc/CHANGES-2018:8: Invalid doc/CHANGES line: \tReworked category/package to 1.2 [author8 2018-01-08]",
+               "WARN: ~/doc/CHANGES-2018:10: Invalid doc/CHANGES line: \ttoo few fields",
+               "WARN: ~/doc/CHANGES-2018:11: Invalid doc/CHANGES line: \ttoo many many many many many fields",
+               "WARN: ~/doc/CHANGES-2018:12: Invalid doc/CHANGES line: \tmissing brackets around author",
+               "WARN: ~/doc/CHANGES-2018:13: Invalid doc/CHANGES line: \tAdded another [new package]")
+}
+
+func (s *Suite) Test_Changes_parseFile__not_found(c *check.C) {
+       t := s.Init(c)
+
+       t.ExpectFatal(
+               func() { G.Pkgsrc.changes.parseFile(t.File("doc/CHANGES-2018"), false) },
+               "FATAL: ~/doc/CHANGES-2018: Cannot be read.")
+}
+
+// Since package authors for pkgsrc-wip cannot necessarily commit to
+// main pkgsrc, don't warn about unsorted doc/CHANGES lines.
+// Only pkgsrc main committers can fix these.
+func (s *Suite) Test_Changes_parseFile__wip_suppresses_warnings(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("wip/package")
+       t.CreateFileLines("doc/CHANGES-2018",
+               CvsID,
+               "",
+               "Changes to the packages collection and infrastructure in 2018:",
+               "",
+               "\tUpdated sysutils/checkperms to 1.10 [rillig 2018-01-05]",
+               "\tUpdated sysutils/checkperms to 1.11 [rillig 2018-01-01]",
+               "\t\tWrong indentation",
+               "\tInvalid pkgpath to 1.16 [rillig 2019-06-16]")
+
+       t.Main("-Cglobal", "-Wall", "wip/package")
+
+       t.CheckOutputLines(
+               "Looks fine.")
+}
+
+// When a single package is checked, only the lines from doc/CHANGES
+// that are related to that package are shown. The others are too
+// unrelated.
+func (s *Suite) Test_Changes_parseFile__default(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package")
+       t.CreateFileLines("doc/CHANGES-2018",
+               CvsID,
+               "",
+               "Changes to the packages collection and infrastructure in 2018:",
+               "",
+               "\tUpdated sysutils/checkperms to 1.10 [rillig 2018-01-05]",
+               "\tUpdated sysutils/checkperms to 1.11 [rillig 2018-01-01]",
+               "\t\tWrong indentation",
+               "\tInvalid pkgpath to 1.16 [rillig 2019-06-16]",
+               "\tUpdated category/package to 2.0 [rillig 2019-07-24]")
+       t.CreateFileLines("Makefile",
+               MkCvsID)
+
+       t.Main("category/package")
+
+       t.CheckOutputLines(
+               "WARN: ~/category/package/Makefile:3: The package is being downgraded from 2.0 (see ../../doc/CHANGES-2018:9) to 1.0.",
+               "1 warning found.",
+               t.Shquote("(Run \"pkglint -e %s\" to show explanations.)", "category/package"))
+
+       // Only when the global checks are enabled, the errors from doc/CHANGES are shown.
+       t.Main("-Cglobal", "-Wall", ".")
+
+       t.CheckOutputLines(
+               "WARN: ~/doc/CHANGES-2018:6: Date \"2018-01-01\" for sysutils/checkperms is earlier than \"2018-01-05\" in line 5.",
+               "WARN: ~/doc/CHANGES-2018:7: Package changes should be indented using a single tab, not \"\\t\\t\".",
+               "WARN: ~/doc/CHANGES-2018:8: Invalid doc/CHANGES line: \tInvalid pkgpath to 1.16 [rillig 2019-06-16]",
+               "WARN: ~/doc/CHANGES-2018:9: Year \"2019\" for category/package does not match the filename CHANGES-2018.",
+               "4 warnings found.",
+               t.Shquote("(Run \"pkglint -e -Cglobal -Wall %s\" to show explanations.)", "."))
+}
+
+func (s *Suite) Test_Changes_parseFile__wrong_indentation(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package")
+       t.CreateFileLines("doc/CHANGES-2018",
+               CvsID,
+               "",
+               "Changes to the packages collection and infrastructure in 2018:",
+               "",
+               "        Updated sysutils/checkperms to 1.10 [rillig 2018-01-05]",
+               "    \tUpdated sysutils/checkperms to 1.11 [rillig 2018-01-01]")
+
+       t.Main("-Cglobal", "-Wall", "category/package")
+
+       t.CheckOutputLines(
+               "WARN: ~/doc/CHANGES-2018:5: Package changes should be indented using a single tab, not \"        \".",
+               "WARN: ~/doc/CHANGES-2018:6: Package changes should be indented using a single tab, not \"    \\t\".",
+               "2 warnings found.",
+               t.Shquote("(Run \"pkglint -e -Cglobal -Wall %s\" to show explanations.)", "category/package"))
+}
+
+// Once or twice in a decade, changes to the pkgsrc infrastructure are also
+// documented in doc/CHANGES. These entries typically span multiple lines.
+func (s *Suite) Test_Changes_parseFile__infrastructure(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("category/package")
+       t.CreateFileLines("doc/CHANGES-2018",
+               CvsID,
+               "",
+               "Changes to the packages collection and infrastructure in 2018:",
+               "",
+               "\tmk/bsd.pkg.mk: Added new framework for handling packages",
+               "\t\twith multiple MASTER_SITES while fetching the main",
+               "\t\tdistfile directly from GitHub [rillig 2018-01-01]",
+               "\tmk/bsd.pkg.mk: Another infrastructure change [rillig 2018-01-02]")
+
+       t.Main("category/package")
+
+       // For pkglint's purpose, the infrastructure entries are simply ignored
+       // since they do not belong to a single package.
+       t.CheckOutputLines(
+               "Looks fine.")
+}
+
+func (s *Suite) Test_Changes_parseFile__old(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpCommandLine("-Cglobal", "-Wall")
+       t.SetUpPkgsrc()
+       t.CreateFileLines("doc/CHANGES-2010",
+               CvsID,
+               "",
+               "Changes to the packages collection and infrastructure in 2015:",
+               "",
+               "\tInvalid line [3 4]")
+       t.CreateFileLines("doc/CHANGES-2015",
+               CvsID,
+               "",
+               "Changes to the packages collection and infrastructure in 2015:",
+               "",
+               "\tUpdated pkgpath to 1.0 [author 2015-07-01]",
+               "\tInvalid line [3 4]",
+               // The date of the below entry is earlier than that of the above entry;
+               // this error is ignored because the 2015 file is too old.
+               "\tUpdated pkgpath to 1.2 [author 2015-02-01]")
+       t.CreateFileLines("doc/CHANGES-2018",
+               CvsID,
+               "",
+               "Changes to the packages collection and infrastructure in 2018:",
+               "",
+               "\tUpdated pkgpath to 1.0 [author date]",
+               "\tUpdated pkgpath to 1.0 [author d]")
+       t.FinishSetUp()
+
+       // The 2010 file is so old that it is skipped completely.
+       // The 2015 file is so old that the date is not checked.
+       // Since 2018, each date in the file must match the filename.
+       t.CheckOutputLines(
+               "WARN: ~/doc/CHANGES-2015:6: Invalid doc/CHANGES line: \tInvalid line [3 4]",
+               "WARN: ~/doc/CHANGES-2018:5: Invalid doc/CHANGES line: \tUpdated pkgpath to 1.0 [author date]",
+               "WARN: ~/doc/CHANGES-2018:6: Invalid doc/CHANGES line: \tUpdated pkgpath to 1.0 [author d]")
+}
+
+func (s *Suite) Test_Changes_parseLine(c *check.C) {
+       t := s.Init(c)
+
+       test := func(text string, diagnostics ...string) {
+               line := t.NewLine("doc/CHANGES-2019", 123, text)
+               _ = (*Changes)(nil).parseLine(line, true)
+               t.CheckOutput(diagnostics)
+       }
+
+       test(CvsID,
+               nil...)
+       test("",
+               nil...)
+       test("Changes to the packages collection and infrastructure in 2019:",
+               nil...)
+
+       test("\tAdded something [author date]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tAdded something [author date]")
+
+       test("\tAdded category/package 1.0 [author 2019-11-17]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tAdded category/package 1.0 [author 2019-11-17]")
+
+       test("\t\tToo large indentation",
+               "WARN: doc/CHANGES-2019:123: Package changes should be indented using a single tab, not \"\\t\\t\".")
+       test("\t Too large indentation",
+               "WARN: doc/CHANGES-2019:123: Package changes should be indented using a single tab, not \"\\t \".")
+
+       test("\t",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t")
+       test("\t1",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1")
+       test("\t1 2 3 4",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4")
+       test("\t1 2 3 4 5",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5")
+       test("\t1 2 3 4 5 6",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5 6")
+       test("\t1 2 3 4 5 6 7",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 3 4 5 6 7")
+       test("\t1 2 [3 4",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 [3 4")
+       test("\t1 2 [3 4]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \t1 2 [3 4]")
+       test("\tAdded 2 [3 4]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: \tAdded 2 [3 4]")
+
+       test("\tAdded pkgpath version 1.0 [author 2019-01-01]",
+               nil...)
+
+       // "to" is wrong
+       test("\tAdded pkgpath to 1.0 [author 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tAdded pkgpath to 1.0 [author 2019-01-01]")
+
+       test("\tUpdated pkgpath to 1.0 [author 2019-01-01]",
+               nil...)
+
+       // "from" is wrong
+       test("\tUpdated pkgpath from 1.0 [author 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tUpdated pkgpath from 1.0 [author 2019-01-01]")
+
+       test("\tDowngraded pkgpath to 1.0 [author 2019-01-01]",
+               nil...)
+
+       // "from" is wrong
+       test("\tDowngraded pkgpath from 1.0 [author 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tDowngraded pkgpath from 1.0 [author 2019-01-01]")
+
+       test("\tRemoved pkgpath [author 2019-01-01]",
+               nil...)
+
+       test("\tRemoved pkgpath successor pkgpath [author 2019-01-01]",
+               nil...)
+
+       // Since 2020-10-06
+       test("\tRemoved pkgpath version 1.3.4 [author 2019-01-01]",
+               nil...)
+
+       // "and" is wrong
+       test("\tRemoved pkgpath and pkgpath [author 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tRemoved pkgpath and pkgpath [author 2019-01-01]")
+
+       test("\tRenamed pkgpath to other [author 2019-01-01]",
+               nil...)
+
+       // "from" is wrong
+       test("\tRenamed pkgpath from previous [author 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tRenamed pkgpath from previous [author 2019-01-01]")
+
+       test("\tMoved pkgpath to other [author 2019-01-01]",
+               nil...)
+
+       // "from" is wrong
+       test("\tMoved pkgpath from previous [author 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tMoved pkgpath from previous [author 2019-01-01]")
+
+       // "Split" is wrong
+       test("\tSplit pkgpath into a and b [author 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tSplit pkgpath into a and b [author 2019-01-01]")
+
+       // Entries ending in a colon are used for infrastructure changes.
+       test("\tmk: remove support for USE_CROSSBASE [author 2016-06-19]",
+               nil...)
+
+       test("\tAdded category/pkgpath version 1.0 [author-dash 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tAdded category/pkgpath version 1.0 [author-dash 2019-01-01]")
+
+       // The word 'version' must only appear with 'Added', not with 'Updated'.
+       test("\tUpdated category/pkgpath to version 1.0 [author 2019-01-01]",
+               "WARN: doc/CHANGES-2019:123: Invalid doc/CHANGES line: "+
+                       "\tUpdated category/pkgpath to version 1.0 [author 2019-01-01]")
+}
+
+func (s *Suite) Test_Changes_parseAuthorAndDate(c *check.C) {
+       t := s.Init(c)
+
+       test := func(dateAndAuthor string, expectedAuthor, expectedDate string) {
+               fields := strings.Split(dateAndAuthor, " ")
+               authorIn, dateIn := fields[0], fields[1]
+               author, date := (*Changes).parseAuthorAndDate(nil, authorIn, dateIn)
+               t.CheckEquals(author, expectedAuthor)
+               t.CheckEquals(date, expectedDate)
+       }
+
+       test("[author 20!9-01-01]", "", "") // bad digit '!' in year
+       test("[author x019-01-01]", "", "") // bad digit 'x' in year
+       test("[author 2x19-01-01]", "", "") // bad digit 'x' in year
+       test("[author 20x9-01-01]", "", "") // bad digit 'x' in year
+       test("[author 201x-01-01]", "", "") // bad digit 'x' in year
+
+       test("[author 2019/01-01]", "", "") // bad separator '/'
+
+       test("[author 2019-x0-01]", "", "") // bad digit 'x' in month
+       test("[author 2019-0x-01]", "", "") // bad digit 'x' in month
+       test("[author 2019-00-01]", "", "") // bad month '00'
+       test("[author 2019-13-01]", "", "") // bad month '13'
+
+       test("[author 2019-01/01]", "", "") // bad separator '/'
+
+       test("[author 2019-01-x0]", "", "") // bad digit 'x' in day
+       test("[author 2019-01-0x]", "", "") // bad digit 'x' in day
+       test("[author 2019-01-00]", "", "") // bad day '00'
+       test("[author 2019-01-32]", "", "") // bad day '32'
+       // No leap year detection, to keep the code fast.
+       test("[author 2019-02-29]", "author", "2019-02-29") // 2019 is not a leap year.
+
+       test("[author 2019-01-01", "", "")  // missing trailing ']'
+       test("[author 2019-01-01+", "", "") // trailing '+' instead of ']'
+
+       test("[author 2019-01-01]", "author", "2019-01-01")
+}
+
+func (s *Suite) Test_Changes_checkChangeVersion(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("doc/CHANGES-2020",
+               "\tAdded category/package version 1.0 [author1 2020-01-01]",
+               "\tAdded category/package version 1.0 [author1 2020-01-01]",
+               "\tAdded category/package version 2.3 [author1 2020-01-01]",
+               "\tUpdated category/package to 0.9 [author1 2020-01-01]",
+               "\tDowngraded category/package to 1.0 [author1 2020-01-01]",
+               "\tDowngraded category/package to 0.8 [author 2020-01-01]",
+               "\tRenamed category/package to category/renamed [author1 2020-01-01]",
+               "\tMoved category/package to other/renamed [author1 2020-01-01]")
+       t.Chdir("doc")
+
+       G.Pkgsrc.changes.parseFile("CHANGES-2020", true)
+
+       // In line 3 there is no warning about the repeated addition since
+       // the multi-packages (Lua, PHP, Python) may add a package in
+       // several versions to the same PKGPATH.
+       t.CheckOutputLines(
+               "WARN: CHANGES-2020:2: Package \"category/package\" was already added in line 1.",
+               "WARN: CHANGES-2020:4: Updating \"category/package\" from 2.3 in line 3 to 0.9 should increase the version number.",
+               "WARN: CHANGES-2020:5: Downgrading \"category/package\" from 0.9 in line 4 to 1.0 should decrease the version number.")
+}
+
+func (s *Suite) Test_Changes_checkChangeVersionNumber(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("doc/CHANGES-2020",
+               "\tAdded category/package version v1 [author1 2020-01-01]",
+               "\tUpdated category/package to v2 [author1 2020-01-01]",
+               "\tDowngraded category/package to v2 [author1 2020-01-01]",
+               "\tUpdated category/package to 2020/03 [author1 2020-01-01]")
+       t.Chdir("doc")
+
+       G.Pkgsrc.changes.parseFile("CHANGES-2020", true)
+
+       t.CheckOutputLines(
+               "WARN: CHANGES-2020:1: Version number \"v1\" should start with a digit.",
+               "WARN: CHANGES-2020:2: Version number \"v2\" should start with a digit.",
+               "WARN: CHANGES-2020:3: Version number \"v2\" should start with a digit.",
+               "WARN: CHANGES-2020:3: Downgrading \"category/package\" from v2 in line 2 "+
+                       "to v2 should decrease the version number.",
+               "WARN: CHANGES-2020:4: Malformed version number \"2020/03\".")
+}
+
+func (s *Suite) Test_Changes_checkChangeDate(c *check.C) {
+       t := s.Init(c)
+
+       t.CreateFileLines("doc/CHANGES-2020",
+               "\tAdded category/package version 1 [author 2018-01-01]")
+       t.Chdir("doc")
+
+       G.Pkgsrc.changes.parseFile("CHANGES-2020", true)
+
+       t.CheckOutputLines(
+               "WARN: CHANGES-2020:1: " +
+                       "Year \"2018\" for category/package " +
+                       "does not match the filename CHANGES-2020.")
+}
+
+func (s *Suite) Test_Changes_checkRemovedAfterLastFreeze(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpCommandLine("-Wall", "--source")
+       t.CreateFileLines("doc/CHANGES-2019",
+               CvsID,
+               "",
+               "\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
+               "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2019Q1 branch [freezer 2019-06-21]",
+               "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2019Q1 branch [freezer 2019-06-25]",
+               "\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
+               "\tAdded category/added-after version 1.0 [updater 2019-07-01]",
+               "\tMoved category/moved-from to category/moved-to [author 2019-07-02]",
+               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]",
+               "\tUpdated category/still-there to 1.0 [updater 2019-07-04]")
+       t.SetUpPackage("category/still-there")
+       t.FinishSetUp()
+
+       // No error message since -Cglobal is not given.
+       t.CheckOutputEmpty()
+}
+
+func (s *Suite) Test_Changes_checkRemovedAfterLastFreeze__check_global(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpCommandLine("-Wall", "-Cglobal", "--source")
+       t.CreateFileLines("doc/CHANGES-2019",
+               CvsID,
+               "",
+               "\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
+               "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2019Q1 branch [freezer 2019-06-21]",
+               "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2019Q1 branch [freezer 2019-06-25]",
+               "\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
+               "\tAdded category/added-after version 1.0 [updater 2019-07-01]",
+               "\tMoved category/moved-from to category/moved-to [author 2019-07-02]",
+               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]",
+               "\tUpdated category/still-there to 1.0 [updater 2019-07-04]")
+       t.SetUpPackage("category/still-there")
+       t.FinishSetUp()
+
+       // It doesn't matter whether the last visible package change was before
+       // or after the latest freeze. The crucial point is that the most
+       // interesting change is the invisible one, which is the removal.
+       // And for finding the removal reliably, it doesn't matter how long ago
+       // the last package change was.
+
+       t.CheckOutputLines(
+               ">\t\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
+               "ERROR: ~/doc/CHANGES-2019:3: Package category/updated-before "+
+                       "must either exist or be marked as removed.",
+               "",
+               ">\t\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
+               "ERROR: ~/doc/CHANGES-2019:6: Package category/updated-after "+
+                       "must either exist or be marked as removed.",
+               "",
+               ">\t\tAdded category/added-after version 1.0 [updater 2019-07-01]",
+               "ERROR: ~/doc/CHANGES-2019:7: Package category/added-after "+
+                       "must either exist or be marked as removed.",
+               "",
+               ">\t\tDowngraded category/downgraded to 1.0 [author 2019-07-03]",
+               "ERROR: ~/doc/CHANGES-2019:9: Package category/downgraded "+
+                       "must either exist or be marked as removed.")
+}
+
+func (s *Suite) Test_Changes_checkRemovedAfterLastFreeze__wip(c *check.C) {
+       t := s.Init(c)
+
+       t.SetUpPackage("wip/package")
+       t.CreateFileLines("doc/CHANGES-2019",
+               CvsID,
+               "",
+               "\tUpdated category/updated-before to 1.0 [updater 2019-04-01]",
+               "\tmk/bsd.pkg.mk: started freeze for pkgsrc-2019Q1 branch [freezer 2019-06-21]",
+               "\tmk/bsd.pkg.mk: freeze ended for pkgsrc-2019Q1 branch [freezer 2019-06-25]",
+               "\tUpdated category/updated-after to 1.0 [updater 2019-07-01]",
+               "\tAdded category/added-after version 1.0 [updater 2019-07-01]",
+               "\tMoved category/moved-from to category/moved-to [author 2019-07-02]",
+               "\tDowngraded category/downgraded to 1.0 [author 2019-07-03]")
+
+       t.Main("-Wall", "--source", "wip/package")
+
+       // Since the first argument is in pkgsrc-wip, the check for doc/CHANGES
+       // is skipped. It may well be that a pkgsrc-wip developer doesn't have
+       // write access to main pkgsrc, and therefore cannot fix doc/CHANGES.
+
+       t.CheckOutputLines(
+               "Looks fine.")
+}
+
+func (s *Suite) Test_Change_Version(c *check.C) {
+       t := s.Init(c)
+
+       loc := Location{"doc/CHANGES-2019", 5}
+       added := Change{loc, Added, "category/path", "1.0", "author", "2019-01-01"}
+       updated := Change{loc, Updated, "category/path", "1.0", "author", "2019-01-01"}
+       downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
+       removed := Change{loc, Removed, "category/path", "1.0", "author", "2019-01-01"}
+
+       t.CheckEquals(added.Version(), "1.0")
+       t.CheckEquals(updated.Version(), "1.0")
+       t.CheckEquals(downgraded.Version(), "1.0")
+       t.ExpectAssert(func() { removed.Version() })
+}
+
+func (s *Suite) Test_Change_Target(c *check.C) {
+       t := s.Init(c)
+
+       loc := Location{"doc/CHANGES-2019", 5}
+       renamed := Change{loc, Renamed, "category/path", "category/other", "author", "2019-01-01"}
+       moved := Change{loc, Moved, "category/path", "category/other", "author", "2019-01-01"}
+       downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
+
+       t.CheckEquals(renamed.Target(), NewPkgsrcPath("category/other"))
+       t.CheckEquals(moved.Target(), NewPkgsrcPath("category/other"))
+       t.ExpectAssert(func() { downgraded.Target() })
+}
+
+func (s *Suite) Test_Change_SuccessorOrVersion(c *check.C) {
+       t := s.Init(c)
+
+       loc := Location{"doc/CHANGES-2019", 5}
+       removed := Change{loc, Removed, "category/path", "", "author", "2019-01-01"}
+       removedSucc := Change{loc, Removed, "category/path", "category/successor", "author", "2019-01-01"}
+       removedVersion := Change{loc, Removed, "category/path", "1.3.4", "author", "2019-01-01"}
+       downgraded := Change{loc, Downgraded, "category/path", "1.0", "author", "2019-01-01"}
+
+       t.CheckEquals(removed.SuccessorOrVersion(), "")
+       t.CheckEquals(removedSucc.SuccessorOrVersion(), "category/successor")
+       t.CheckEquals(removedVersion.SuccessorOrVersion(), "1.3.4")
+       t.ExpectAssert(func() { downgraded.SuccessorOrVersion() })
+}
+
+func (s *Suite) Test_Change_IsAbove(c *check.C) {
+       t := s.Init(c)
+
+       var changes = []*Change{
+               {Location{"", 1}, 0, "", "", "", "2011-07-01"},
+               {Location{"", 2}, 0, "", "", "", "2011-07-01"},
+               {Location{"", 1}, 0, "", "", "", "2011-07-02"}}
+
+       test := func(i int, chi *Change, j int, chj *Change) {
+               actual := chi.IsAbove(chj)
+               expected := i < j
+               if actual != expected {
+                       t.CheckDeepEquals(
+                               []interface{}{i, *chi, j, *chj, actual},
+                               []interface{}{i, *chi, j, *chj, expected})
+               }
+       }
+
+       for i, chi := range changes {
+               for j, chj := range changes {
+                       test(i, chi, j, chj)
+               }
+       }
+}
+
+func (s *Suite) Test_ParseChangeAction(c *check.C) {
+       t := s.Init(c)
+
+       t.CheckEquals(ParseChangeAction("Added"), Added)
+       t.CheckEquals(ParseChangeAction("Unknown"), ChangeAction(0))
+}
+
+func (s *Suite) Test_ChangeAction_String(c *check.C) {
+       t := s.Init(c)
+
+       t.CheckEquals(Added.String(), "Added")
+       t.CheckEquals(Removed.String(), "Removed")
+}



Home | Main Index | Thread Index | Old Index