diff options
author | rillig <rillig@pkgsrc.org> | 2018-01-07 01:13:21 +0000 |
---|---|---|
committer | rillig <rillig@pkgsrc.org> | 2018-01-07 01:13:21 +0000 |
commit | ee64646636a9c97491f57e81323ebcfed92dfbe1 (patch) | |
tree | de9059f30ec1ffecf3aefb9381c143b2c4b46989 /pkgtools/pkglint | |
parent | 6960bb4220afd6ec895cab6553afe7b9d026b2e0 (diff) | |
download | pkgsrc-ee64646636a9c97491f57e81323ebcfed92dfbe1.tar.gz |
Updated pkglint to 5.4.25.
Changes since 5.4.24:
* More specific warning for "exitcode with pipe shell commands"
* Don't warn that the echo in "echo | sed" could fail
* Allow packages to define custom make targets
* Don't warn about a misplaced LICENSE when a package doesn't define it
* Skip .git directories
* Reduce number of hicolor-icon-theme error messages in PLIST files
* Remove MKCRYPTO, USE_CRYPTO, CRYPTO variable definitions
Diffstat (limited to 'pkgtools/pkglint')
-rw-r--r-- | pkgtools/pkglint/Makefile | 4 | ||||
-rw-r--r-- | pkgtools/pkglint/files/globaldata.go | 14 | ||||
-rw-r--r-- | pkgtools/pkglint/files/mkline_test.go | 6 | ||||
-rw-r--r-- | pkgtools/pkglint/files/mklinechecker.go | 14 | ||||
-rw-r--r-- | pkgtools/pkglint/files/mklines_test.go | 32 | ||||
-rw-r--r-- | pkgtools/pkglint/files/package.go | 22 | ||||
-rw-r--r-- | pkgtools/pkglint/files/package_test.go | 29 | ||||
-rw-r--r-- | pkgtools/pkglint/files/pkglint.go | 2 | ||||
-rw-r--r-- | pkgtools/pkglint/files/plist.go | 15 | ||||
-rw-r--r-- | pkgtools/pkglint/files/plist_test.go | 9 | ||||
-rw-r--r-- | pkgtools/pkglint/files/shell.go | 53 | ||||
-rw-r--r-- | pkgtools/pkglint/files/shell_test.go | 37 | ||||
-rw-r--r-- | pkgtools/pkglint/files/shtypes.go | 8 | ||||
-rw-r--r-- | pkgtools/pkglint/files/util.go | 29 |
14 files changed, 232 insertions, 42 deletions
diff --git a/pkgtools/pkglint/Makefile b/pkgtools/pkglint/Makefile index d0c3799fa35..a9589d003a6 100644 --- a/pkgtools/pkglint/Makefile +++ b/pkgtools/pkglint/Makefile @@ -1,6 +1,6 @@ -# $NetBSD: Makefile,v 1.521 2018/01/02 08:13:15 maya Exp $ +# $NetBSD: Makefile,v 1.522 2018/01/07 01:13:21 rillig Exp $ -PKGNAME= pkglint-5.4.24 +PKGNAME= pkglint-5.4.25 DISTFILES= # none CATEGORIES= pkgtools diff --git a/pkgtools/pkglint/files/globaldata.go b/pkgtools/pkglint/files/globaldata.go index 66cc758af78..9af8b61a1b5 100644 --- a/pkgtools/pkglint/files/globaldata.go +++ b/pkgtools/pkglint/files/globaldata.go @@ -587,6 +587,20 @@ func (tr *ToolRegistry) RegisterTool(tool *Tool) { } } +func (tr *ToolRegistry) FindByCommand(cmd *ShToken) *Tool { + if tool := tr.byName[cmd.MkText]; tool != nil { + return tool + } + if len(cmd.Atoms) == 1 { + if varuse := cmd.Atoms[0].VarUse(); varuse != nil { + if tool := tr.byVarname[varuse.varname]; tool != nil { + return tool + } + } + } + return nil +} + func (tr *ToolRegistry) Trace() { if trace.Tracing { defer trace.Call0()() diff --git a/pkgtools/pkglint/files/mkline_test.go b/pkgtools/pkglint/files/mkline_test.go index 9b84891b0e7..5327d4b170e 100644 --- a/pkgtools/pkglint/files/mkline_test.go +++ b/pkgtools/pkglint/files/mkline_test.go @@ -383,7 +383,7 @@ func (s *Suite) Test_MkLine_variableNeedsQuoting__command_in_command(c *check.C) MkLineChecker{G.Mk.mklines[1]}.Check() s.CheckOutputLines( - "WARN: Makefile:2: The exitcode of the left-hand-side command of the pipe operator is ignored.") + "WARN: Makefile:2: The exitcode of \"${FIND}\" at the left of the | operator is ignored.") } func (s *Suite) Test_MkLine_variableNeedsQuoting__word_as_part_of_word(c *check.C) { @@ -420,8 +420,8 @@ func (s *Suite) Test_MkLine_variableNeedsQuoting__command_as_command_argument(c MkLineChecker{G.Mk.mklines[2]}.Check() s.CheckOutputLines( - "WARN: Makefile:2: The exitcode of the left-hand-side command of the pipe operator is ignored.", - "WARN: Makefile:3: The exitcode of the left-hand-side command of the pipe operator is ignored.") + "WARN: Makefile:2: The exitcode of the command at the left of the | operator is ignored.", + "WARN: Makefile:3: The exitcode of the command at the left of the | operator is ignored.") } // Based on mail/mailfront/Makefile. diff --git a/pkgtools/pkglint/files/mklinechecker.go b/pkgtools/pkglint/files/mklinechecker.go index bf69746a456..f268d8f0aa5 100644 --- a/pkgtools/pkglint/files/mklinechecker.go +++ b/pkgtools/pkglint/files/mklinechecker.go @@ -218,12 +218,20 @@ func (ck MkLineChecker) checkDependencyRule(allowedTargets map[string]bool) { } else if target == ".ORDER" { // TODO: Check for spelling mistakes. + } else if hasPrefix(target, "${.CURDIR}/") { + // OK, this is intentional + } else if !allowedTargets[target] { mkline.Warnf("Unusual target %q.", target) Explain( - "If you want to define your own targets, you can \"declare\"", - "them by inserting a \".PHONY: my-target\" line before this line. This", - "will tell make(1) to not interpret this target's name as a filename.") + "If you want to define your own target, declare it like this:", + "", + "\t.PHONY: my-target", + "", + "In the rare case that you actually want a file-based make(1)", + "target, write it like this:", + "", + "\t${.CURDIR}/my-filename:") } } } diff --git a/pkgtools/pkglint/files/mklines_test.go b/pkgtools/pkglint/files/mklines_test.go index 767d5e9eebf..6f46a0e8483 100644 --- a/pkgtools/pkglint/files/mklines_test.go +++ b/pkgtools/pkglint/files/mklines_test.go @@ -1,7 +1,7 @@ package main import ( - check "gopkg.in/check.v1" + "gopkg.in/check.v1" ) const mkrcsid = "# $" + "NetBSD$" @@ -188,7 +188,7 @@ func (s *Suite) Test_MkLines__for_loop_multiple_variables(c *check.C) { s.CheckOutputLines( "WARN: audio/squeezeboxserver/Makefile:3: Variable names starting with an underscore (_list_) are reserved for internal pkgsrc use.", "WARN: audio/squeezeboxserver/Makefile:3: Variable names starting with an underscore (_dir_) are reserved for internal pkgsrc use.", - "WARN: audio/squeezeboxserver/Makefile:4: The exitcode of the left-hand-side command of the pipe operator is ignored.") + "WARN: audio/squeezeboxserver/Makefile:4: The exitcode of \"${FIND}\" at the left of the | operator is ignored.") } func (s *Suite) Test_MkLines__comparing_YesNo_variable_to_string(c *check.C) { @@ -479,3 +479,31 @@ func (s *Suite) Test_MkLines_Check_indentation(c *check.C) { "ERROR: options.mk:15: Unmatched .endif.\n"+ "NOTE: options.mk:15: This directive should be indented by 0 spaces.\n") } + +// Demonstrates how to define your own make(1) targets. +func (s *Suite) Test_MkLines_wip_category_Makefile(c *check.C) { + s.Init(c) + s.UseCommandLine("-Wall") + G.globalData.InitVartypes() + s.RegisterTool(&Tool{Name: "rm", Varname: "RM", Predefined: true}) + mklines := s.NewMkLines("Makefile", + mkrcsid, + "", + "COMMENT=\tWIP pkgsrc packages", + "", + "SUBDIR+=\taaa", + "SUBDIR+=\tzzz", + "", + "${.CURDIR}/PKGDB:", + "\t${RM} -f ${.CURDIR}/PKGDB", + "", + "${.CURDIR}/INDEX:", + "\t${RM} -f ${.CURDIR}/INDEX", + "", + ".include \"../../mk/misc/category.mk\"") + + mklines.Check() + + c.Check(s.Output(), equals, ""+ + "ERROR: Makefile:14: \"/mk/misc/category.mk\" does not exist.\n") +} diff --git a/pkgtools/pkglint/files/package.go b/pkgtools/pkglint/files/package.go index ca52bdd224b..609f9d0fac5 100644 --- a/pkgtools/pkglint/files/package.go +++ b/pkgtools/pkglint/files/package.go @@ -35,6 +35,7 @@ type Package struct { loadTimeTools map[string]bool // true=ok, false=not ok, absent=not mentioned in USE_TOOLS. conditionalIncludes map[string]MkLine unconditionalIncludes map[string]MkLine + once Once } func NewPackage(pkgpath string) *Package { @@ -398,7 +399,7 @@ func (pkg *Package) checkfilePackageMakefile(fname string, mklines *MkLines) { perlLine.Warnf("REPLACE_PERL is ignored when NO_CONFIGURE is set (in %s)", noconfLine.ReferenceFrom(perlLine.Line)) } - if vardef["LICENSE"] == nil && vardef["META_PACKAGE"] == nil { + if vardef["LICENSE"] == nil && vardef["META_PACKAGE"] == nil && pkg.once.FirstTime("LICENSE") { NewLineWhole(fname).Errorf("Each package must define its LICENSE.") } @@ -756,16 +757,19 @@ func (pkg *Package) ChecklinesPackageMakefileVarorder(mklines *MkLines) { default: for varindex < len(vars) { + varname := vars[varindex].varname if vars[varindex].count == once && !maySkipSection { - line.Warnf("The canonical position for the required variable %s is here.", vars[varindex].varname) - Explain( - "In simple package Makefiles, some common variables should be", - "arranged in a specific order.", - "", - "See doc/Makefile-example or the pkgsrc guide, section", - "\"Package components\", subsection \"Makefile\" for more information.") + if varname != "LICENSE" || pkg.once.FirstTime("LICENSE") { + line.Warnf("The canonical position for the required variable %s is here.", varname) + Explain( + "In simple package Makefiles, some common variables should be", + "arranged in a specific order.", + "", + "See doc/Makefile-example or the pkgsrc guide, section", + "\"Package components\", subsection \"Makefile\" for more information.") + } } - below[vars[varindex].varname] = belowWhat + below[varname] = belowWhat varindex++ } nextSection = true diff --git a/pkgtools/pkglint/files/package_test.go b/pkgtools/pkglint/files/package_test.go index 0cfce3b221b..0dce87c0aa7 100644 --- a/pkgtools/pkglint/files/package_test.go +++ b/pkgtools/pkglint/files/package_test.go @@ -48,6 +48,35 @@ func (s *Suite) Test_Package_ChecklinesPackageMakefileVarorder(c *check.C) { "WARN: Makefile:6: The canonical position for the required variable LICENSE is here.") } +func (s *Suite) Test_Package_varorder_license(c *check.C) { + s.Init(c) + s.UseCommandLine("-Worder") + + s.CreateTmpFileLines("mk/bsd.pkg.mk", "# dummy") + s.CreateTmpFileLines("x11/Makefile", mkrcsid) + s.CreateTmpFileLines("x11/9term/PLIST", "@comment $"+"NetBSD$", "bin/9term") + s.CreateTmpFileLines("x11/9term/distinfo", "$"+"NetBSD$") + s.CreateTmpFileLines("x11/9term/Makefile", + mkrcsid, + "", + "DISTNAME=9term-1.0", + "CATEGORIES=x11", + "", + "COMMENT=Terminal", + "", + ".include \"../../mk/bsd.pkg.mk\"") + + G.globalData.InitVartypes() + G.globalData.Pkgsrcdir = s.TmpDir() + G.CurrentDir = s.TmpDir() + + (&Pkglint{}).CheckDirent(s.TmpDir() + "/x11/9term") + + // Since the error is grave enough, the warning about the correct position is suppressed. + s.CheckOutputLines( + "ERROR: ~/x11/9term/Makefile: Each package must define its LICENSE.") +} + // https://mail-index.netbsd.org/tech-pkg/2017/01/18/msg017698.html func (s *Suite) Test_Package_ChecklinesPackageMakefileVarorder__MASTER_SITES(c *check.C) { s.Init(c) diff --git a/pkgtools/pkglint/files/pkglint.go b/pkgtools/pkglint/files/pkglint.go index 17f083c5b51..614ed54638c 100644 --- a/pkgtools/pkglint/files/pkglint.go +++ b/pkgtools/pkglint/files/pkglint.go @@ -384,7 +384,7 @@ func Checkfile(fname string) { switch { case st.Mode().IsDir(): switch { - case basename == "files" || basename == "patches" || basename == "CVS": + case basename == "files" || basename == "patches" || isIgnoredFilename(basename): // Ok case matches(fname, `(?:^|/)files/[^/]*$`): // Ok diff --git a/pkgtools/pkglint/files/plist.go b/pkgtools/pkglint/files/plist.go index 6c7dc9f3a3b..cd18537e397 100644 --- a/pkgtools/pkglint/files/plist.go +++ b/pkgtools/pkglint/files/plist.go @@ -33,15 +33,15 @@ func ChecklinesPlist(lines []Line) { make(map[string]*PlistLine), make(map[string]*PlistLine), "", - false} + Once{}} ck.Check(lines) } type PlistChecker struct { - allFiles map[string]*PlistLine - allDirs map[string]*PlistLine - lastFname string - warnedAboutIconThemes bool + allFiles map[string]*PlistLine + allDirs map[string]*PlistLine + lastFname string + once Once } type PlistLine struct { @@ -365,7 +365,7 @@ func (ck *PlistChecker) checkpathShare(pline *PlistLine) { case hasPrefix(text, "share/icons/") && G.Pkg != nil: if hasPrefix(text, "share/icons/hicolor/") && G.Pkg.Pkgpath != "graphics/hicolor-icon-theme" { f := "../../graphics/hicolor-icon-theme/buildlink3.mk" - if G.Pkg.included[f] == nil { + if G.Pkg.included[f] == nil && ck.once.FirstTime("hicolor-icon-theme") { line.Errorf("Packages that install hicolor icons must include %q in the Makefile.", f) } } @@ -380,9 +380,8 @@ func (ck *PlistChecker) checkpathShare(pline *PlistLine) { } } - if !ck.warnedAboutIconThemes && contains(text[12:], "/") && G.Pkg.vardef["ICON_THEMES"] == nil { + if contains(text[12:], "/") && G.Pkg.vardef["ICON_THEMES"] == nil && ck.once.FirstTime("ICON_THEMES") { line.Warnf("Packages that install icon theme files should set ICON_THEMES.") - ck.warnedAboutIconThemes = true } case hasPrefix(text, "share/doc/html/"): diff --git a/pkgtools/pkglint/files/plist_test.go b/pkgtools/pkglint/files/plist_test.go index c4c674f1381..09dcd634c4e 100644 --- a/pkgtools/pkglint/files/plist_test.go +++ b/pkgtools/pkglint/files/plist_test.go @@ -23,6 +23,8 @@ func (s *Suite) Test_ChecklinesPlist(c *check.C) { "${PLIST.obsolete}@unexec rmdir /tmp", "sbin/clockctl", "share/icons/gnome/delete-icon", + "share/icons/hicolor/icon1.png", + "share/icons/hicolor/icon2.png", // No additional warning "share/tzinfo", "share/tzinfo") @@ -45,7 +47,8 @@ func (s *Suite) Test_ChecklinesPlist(c *check.C) { "WARN: PLIST:13: Manual page missing for sbin/clockctl.", "ERROR: PLIST:14: The package Makefile must include \"../../graphics/gnome-icon-theme/buildlink3.mk\".", "WARN: PLIST:14: Packages that install icon theme files should set ICON_THEMES.", - "ERROR: PLIST:16: Duplicate filename \"share/tzinfo\", already appeared in line 15.") + "ERROR: PLIST:15: Packages that install hicolor icons must include \"../../graphics/hicolor-icon-theme/buildlink3.mk\" in the Makefile.", + "ERROR: PLIST:18: Duplicate filename \"share/tzinfo\", already appeared in line 17.") } func (s *Suite) Test_ChecklinesPlist__empty(c *check.C) { @@ -127,7 +130,7 @@ func (s *Suite) Test_PlistLineSorter_Sort(c *check.C) { "${PLIST.linux}${PLIST.x86_64}lib/lib-linux-x86_64.so", // Double conditional, see graphics/graphviz "lib/after.la", "@exec echo \"after lib/after.la\"") - ck := &PlistChecker{nil, nil, "", false} + ck := &PlistChecker{nil, nil, "", Once{}} plines := ck.NewLines(lines) sorter1 := NewPlistLineSorter(plines) @@ -135,7 +138,7 @@ func (s *Suite) Test_PlistLineSorter_Sort(c *check.C) { cleanedLines := append(append(lines[0:5], lines[6:8]...), lines[9:]...) // Remove ${UNKNOWN} and @exec - sorter2 := NewPlistLineSorter((&PlistChecker{nil, nil, "", false}).NewLines(cleanedLines)) + sorter2 := NewPlistLineSorter((&PlistChecker{nil, nil, "", Once{}}).NewLines(cleanedLines)) c.Check(sorter2.unsortable, check.IsNil) diff --git a/pkgtools/pkglint/files/shell.go b/pkgtools/pkglint/files/shell.go index e45be3c6d1a..ac6b1450930 100644 --- a/pkgtools/pkglint/files/shell.go +++ b/pkgtools/pkglint/files/shell.go @@ -767,14 +767,53 @@ func (scc *ShellProgramChecker) checkPipeExitcode(line Line, pipeline *MkShPipel defer trace.Call()() } + oneOf := func(s string, others ...string) bool { + for _, other := range others { + if s == other { + return true + } + } + return false + } + + canFail := func() (bool, string) { + for _, cmd := range pipeline.Cmds[:len(pipeline.Cmds)-1] { + simple := cmd.Simple + if simple == nil { + return true, "" + } + if len(simple.Redirections) != 0 { + return true, simple.Name.MkText + } + tool := G.globalData.Tools.FindByCommand(simple.Name) + switch { + case tool == nil: + return true, simple.Name.MkText + case oneOf(tool.Name, "echo", "printf"): + case oneOf(tool.Name, "sed", "gsed", "grep", "ggrep") && len(simple.Args) == 1: + break + default: + return true, simple.Name.MkText + } + } + return false, "" + } + if G.opts.WarnExtra && len(pipeline.Cmds) > 1 { - line.Warnf("The exitcode of the left-hand-side command of the pipe operator is ignored.") - Explain( - "In a shell command like \"cat *.txt | grep keyword\", if the command", - "on the left side of the \"|\" fails, this failure is ignored.", - "", - "If you need to detect the failure of the left-hand-side command, use", - "temporary files to save the output of the command.") + if canFail, cmd := canFail(); canFail { + if cmd != "" { + line.Warnf("The exitcode of %q at the left of the | operator is ignored.", cmd) + } else { + line.Warnf("The exitcode of the command at the left of the | operator is ignored.") + } + Explain( + "In a shell command like \"cat *.txt | grep keyword\", if the command", + "on the left side of the \"|\" fails, this failure is ignored.", + "", + "If you need to detect the failure of the left-hand-side command, use", + "temporary files to save the output of the command. A good place to", + "create those files is in ${WRKDIR}.") + } } } diff --git a/pkgtools/pkglint/files/shell_test.go b/pkgtools/pkglint/files/shell_test.go index dd57e43dcee..09d10cf947e 100644 --- a/pkgtools/pkglint/files/shell_test.go +++ b/pkgtools/pkglint/files/shell_test.go @@ -179,7 +179,7 @@ func (s *Suite) Test_ShellLine_CheckShellCommandLine(c *check.C) { shline.CheckShellCommandLine("${RUN} subdir=\"`unzip -c \"$$e\" install.rdf | awk '/re/ { print \"hello\" }'`\"") s.CheckOutputLines( - "WARN: fname:1: The exitcode of the left-hand-side command of the pipe operator is ignored.", + "WARN: fname:1: The exitcode of \"unzip\" at the left of the | operator is ignored.", "WARN: fname:1: Unknown shell command \"unzip\".", "WARN: fname:1: Unknown shell command \"awk\".") @@ -194,7 +194,7 @@ func (s *Suite) Test_ShellLine_CheckShellCommandLine(c *check.C) { s.CheckOutputLines( "WARN: fname:1: XPI_FILES is used but not defined. Spelling mistake?", - "WARN: fname:1: The exitcode of the left-hand-side command of the pipe operator is ignored.", + "WARN: fname:1: The exitcode of \"${UNZIP_CMD}\" at the left of the | operator is ignored.", "WARN: fname:1: UNZIP_CMD is used but not defined. Spelling mistake?", "WARN: fname:1: Unknown shell command \"awk\".", "WARN: fname:1: Unknown shell command \"${MKDIR}\".", @@ -263,6 +263,39 @@ func (s *Suite) Test_ShellLine_CheckShellCommandLine__show_autofix(c *check.C) { "AUTOFIX: Makefile:1: Replacing \"${PKGNAME:Q}\" with \"${PKGNAME}\".") } +// Simple commands like echo(1) or printf(1) are assumed to never fail. +func (s *Suite) Test_ShellLine_CheckShellCommandLine__exitcode(c *check.C) { + s.Init(c) + s.UseCommandLine("-Wall") + G.globalData.InitVartypes() + s.RegisterTool(&Tool{Name: "cat", Predefined: true}) + s.RegisterTool(&Tool{Name: "echo", Predefined: true}) + s.RegisterTool(&Tool{Name: "printf", Predefined: true}) + s.RegisterTool(&Tool{Name: "sed", Predefined: true}) + s.RegisterTool(&Tool{Name: "right-side", Predefined: true}) + G.Mk = s.NewMkLines("Makefile", + "\t echo | right-side", + "\t sed s,s,s, | right-side", + "\t printf | sed s,s,s, | right-side ", + "\t cat | right-side", + "\t cat | echo | right-side", + "\t echo | cat | right-side", + "\t sed s,s,s, filename | right-side", + "\t sed s,s,s < input | right-side") + + for _, mkline := range G.Mk.mklines { + shline := NewShellLine(mkline) + shline.CheckShellCommandLine(mkline.Shellcmd()) + } + + s.CheckOutputLines( + "WARN: Makefile:4: The exitcode of \"cat\" at the left of the | operator is ignored.", + "WARN: Makefile:5: The exitcode of \"cat\" at the left of the | operator is ignored.", + "WARN: Makefile:6: The exitcode of \"cat\" at the left of the | operator is ignored.", + "WARN: Makefile:7: The exitcode of \"sed\" at the left of the | operator is ignored.", + "WARN: Makefile:8: The exitcode of \"sed\" at the left of the | operator is ignored.") +} + func (s *Suite) Test_ShellLine_CheckShellCommandLine__autofix(c *check.C) { s.Init(c) s.UseCommandLine("-Wall", "--autofix") diff --git a/pkgtools/pkglint/files/shtypes.go b/pkgtools/pkglint/files/shtypes.go index 1c37844b8cf..084fc6b67ac 100644 --- a/pkgtools/pkglint/files/shtypes.go +++ b/pkgtools/pkglint/files/shtypes.go @@ -55,6 +55,14 @@ func (token *ShAtom) String() string { return fmt.Sprintf("ShAtom(%v, %q, %s)", token.Type, token.MkText, token.Quoting) } +// Returns nil for plain shell tokens. +func (atom *ShAtom) VarUse() *MkVarUse { + if atom.Type == shtVaruse { + return atom.Data.(*MkVarUse) + } + return nil +} + // ShQuoting describes the context in which a string appears // and how it must be unescaped to get its literal value. type ShQuoting uint8 diff --git a/pkgtools/pkglint/files/util.go b/pkgtools/pkglint/files/util.go index 7961efd69dd..aaf8f19bfae 100644 --- a/pkgtools/pkglint/files/util.go +++ b/pkgtools/pkglint/files/util.go @@ -68,7 +68,7 @@ func isEmptyDir(fname string) bool { } for _, dirent := range dirents { name := dirent.Name() - if name == "." || name == ".." || name == "CVS" { + if isIgnoredFilename(name) { continue } if dirent.IsDir() && isEmptyDir(fname+"/"+name) { @@ -88,13 +88,21 @@ func getSubdirs(fname string) []string { var subdirs []string for _, dirent := range dirents { name := dirent.Name() - if name != "." && name != ".." && name != "CVS" && dirent.IsDir() && !isEmptyDir(fname+"/"+name) { + if dirent.IsDir() && !isIgnoredFilename(name) && !isEmptyDir(fname+"/"+name) { subdirs = append(subdirs, name) } } return subdirs } +func isIgnoredFilename(fileName string) bool { + switch fileName { + case ".", "..", "CVS", ".svn", ".git", ".hg": + return true + } + return false +} + // Checks whether a file is already committed to the CVS repository. func isCommitted(fname string) bool { lines := loadCvsEntries(fname) @@ -333,3 +341,20 @@ func hasAlnumPrefix(s string) bool { b := s[0] return '0' <= b && b <= '9' || 'A' <= b && b <= 'Z' || b == '_' || 'a' <= b && b <= 'z' } + +// Once remembers with which arguments its FirstTime method has been called +// and only returns true on each first call. +type Once struct { + seen map[string]bool +} + +func (o *Once) FirstTime(what string) bool { + if o.seen == nil { + o.seen = make(map[string]bool) + } + if _, ok := o.seen[what]; ok { + return false + } + o.seen[what] = true + return true +} |