doc lint: fix bugs in, simplify and improve lint script

The lint-gitlink.perl script added in ab81411ced (ci: validate "linkgit:" in documentation, 2016-05-04) was more complex than it needed to be. It: - Was using File::Find to recursively find *.txt files in Documentation/, let's instead use the Makefile as a source of truth for *.txt files, and pass it down to the script. - We now don't lint linkgit:* in RelNotes/* or technical/*, which we shouldn't have been doing in the first place anyway. - When the doc-diff script was added in beb188e22a (add a script to diff rendered documentation, 2018-08-06) we started sometimes having a "git worktree" under Documentation/. This tree contains a full checkout of git.git, as a result the "lint" script would recurse into that, and lint any *.txt file found in that entire repository. In practice the only in-tree "linkgit" outside of the Documentation/ tree is contrib/contacts/git-contacts.txt and contrib/subtree/git-subtree.txt, so this wouldn't emit any errors Now we instead simply trust the Makefile to give us *.txt files. Since the Makefile also knows what sections each page should be in we don't have to open the files ourselves and try to parse that out. As a bonus this will also catch bugs with the section line in the files themselves being incorrect. The structure of the new script is mostly based on t/check-non-portable-shell.pl. As an added bonus it will also use pos() to print where the problems it finds are, e.g. given an issue like: diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt [...] and line numbers. git-cherry therefore detects when commits have been -"copied" by means of linkgit:git-cherry-pick[1], linkgit:git-am[1] or -linkgit:git-rebase[1]. +"copied" by means of linkgit:git-cherry-pick[2], linkgit:git-am[3] or +linkgit:git-rebase[4]. We'll now emit: git-cherry.txt:20: error: git-cherry-pick[2]: wrong section (should be 1), shown with 'HERE' below: git-cherry.txt:20: '"copied" by means of linkgit:git-cherry-pick[2]' <-- HERE git-cherry.txt:20: error: git-am[3]: wrong section (should be 1), shown with 'HERE' below: git-cherry.txt:20: '"copied" by means of linkgit:git-cherry-pick[2], linkgit:git-am[3]' <-- HERE git-cherry.txt:21: error: git-rebase[4]: wrong section (should be 1), shown with 'HERE' below: git-cherry.txt:21: 'linkgit:git-rebase[4]' <-- HERE Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Apr 9, 2021 at 17:02 UTC d2c9908076b00aa59037673e7f262fc6f8867559
2 files changed +55 -57
Documentation/Makefile
+5 -1
@@ -478,7 +478,11 @@ print-man1:
478 @for i in $(MAN1_TXT); do echo $$i; done
479
480 lint-docs::
481 - $(QUIET_LINT)$(PERL_PATH) lint-gitlink.perl
481 + $(QUIET_LINT)$(PERL_PATH) lint-gitlink.perl \
482 + $(HOWTO_TXT) $(DOC_DEP_TXT) \
483 + --section=1 $(MAN1_TXT) \
484 + --section=5 $(MAN5_TXT) \
485 + --section=7 $(MAN7_TXT)
486
487 ifeq ($(wildcard po/Makefile),po/Makefile)
488 doc-l10n install-l10n::
Documentation/lint-gitlink.perl
+50 -56
@@ -2,72 +2,66 @@
2
3 use strict;
4 use warnings;
5 -use File::Find;
6 -use Getopt::Long;
5
8 -my $basedir = ".";
9 -GetOptions("basedir=s" => \$basedir)
10 - or die("Cannot parse command line arguments\n");
6 +# Parse arguments, a simple state machine for input like:
7 +#
8 +# howto/*.txt config/*.txt --section=1 git.txt git-add.txt [...] --to-lint git-add.txt a-file.txt [...]
9 +my %TXT;
10 +my %SECTION;
11 +my $section;
12 +my $lint_these = 0;
13 +for my $arg (@ARGV) {
14 + if (my ($sec) = $arg =~ /^--section=(\d+)$/s) {
15 + $section = $sec;
16 + next;
17 + }
18
12 -my $found_errors = 0;
19 + my ($name) = $arg =~ /^(.*?)\.txt$/s;
20 + unless (defined $section) {
21 + $TXT{$name} = $arg;
22 + next;
23 + }
24
14 -sub report {
15 - my ($where, $what, $error) = @_;
16 - print "$where: $error: $what\n";
17 - $found_errors = 1;
25 + $SECTION{$name} = $section;
26 }
27
20 -sub grab_section {
21 - my ($page) = @_;
22 - open my $fh, "<", "$basedir/$page.txt";
23 - my $firstline = <$fh>;
24 - chomp $firstline;
25 - close $fh;
26 - my ($section) = ($firstline =~ /.*\((\d)\)$/);
27 - return $section;
28 +my $exit_code = 0;
29 +sub report {
30 + my ($pos, $line, $target, $msg) = @_;
31 + substr($line, $pos) = "' <-- HERE";
32 + $line =~ s/^\s+//;
33 + print "$ARGV:$.: error: $target: $msg, shown with 'HERE' below:\n";
34 + print "$ARGV:$.:\t'$line\n";
35 + $exit_code = 1;
36 }
37
30 -sub lint {
31 - my ($file) = @_;
32 - open my $fh, "<", $file
33 - or return;
34 - while (<$fh>) {
35 - my $where = "$file:$.";
36 - while (s/linkgit:((.*?)\[(\d)\])//) {
37 - my ($target, $page, $section) = ($1, $2, $3);
38 +@ARGV = sort values %TXT;
39 +die "BUG: Nothing to process!" unless @ARGV;
40 +while (<>) {
41 + my $line = $_;
42 + while ($line =~ m/linkgit:((.*?)\[(\d)\])/g) {
43 + my $pos = pos $line;
44 + my ($target, $page, $section) = ($1, $2, $3);
45
39 - # De-AsciiDoc
40 - $page =~ s/{litdd}/--/g;
46 + # De-AsciiDoc
47 + $page =~ s/{litdd}/--/g;
48
42 - if ($page !~ /^git/) {
43 - report($where, $target, "nongit link");
44 - next;
45 - }
46 - if (! -f "$basedir/$page.txt") {
47 - report($where, $target, "no such source");
48 - next;
49 - }
50 - my $real_section = grab_section($page);
51 - if ($real_section != $section) {
52 - report($where, $target,
53 - "wrong section (should be $real_section)");
54 - next;
55 - }
49 + if (!exists $TXT{$page}) {
50 + report($pos, $line, $target, "link outside of our own docs");
51 + next;
52 + }
53 + if (!exists $SECTION{$page}) {
54 + report($pos, $line, $target, "link outside of our sectioned docs");
55 + next;
56 + }
57 + my $real_section = $SECTION{$page};
58 + if ($section != $SECTION{$page}) {
59 + report($pos, $line, $target, "wrong section (should be $real_section)");
60 + next;
61 }
62 }
58 - close $fh;
59 -}
60 -
61 -sub lint_it {
62 - lint($File::Find::name) if -f && /\.txt$/;
63 -}
64 -
65 -if (!@ARGV) {
66 - find({ wanted => \&lint_it, no_chdir => 1 }, $basedir);
67 -} else {
68 - for (@ARGV) {
69 - lint($_);
70 - }
63 + # this resets our $. for each file
64 + close ARGV if eof;
65 }
66
73 -exit $found_errors;
67 +exit $exit_code;