]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Documentation: stop depending on Perl to generate command list
authorPatrick Steinhardt <ps@pks.im>
Wed, 16 Apr 2025 12:16:10 +0000 (14:16 +0200)
committerJunio C Hamano <gitster@pobox.com>
Wed, 16 Apr 2025 14:30:30 +0000 (07:30 -0700)
The "cmd-list.perl" script is used to extract the list of commands part
of a specific category and extracts the description of each command from
its respective manpage. The generated output is then included in git(1)
to list all Git commands.

The script is written in Perl. Refactor it to use shell scripting
exclusively so that we can get rid of the mandatory dependency on Perl
to build our documentation.

The converted script is slower compared to its Perl implementation. But
by being careful and not spawning external commands in `format_one ()`
we can mitigate the performance hit to a reasonable level:

    Benchmark 1: Perl
      Time (mean ± σ):      10.3 ms ±   0.2 ms    [User: 7.0 ms, System: 3.3 ms]
      Range (min … max):    10.0 ms …  11.1 ms    200 runs

    Benchmark 2: Shell
      Time (mean ± σ):      74.4 ms ±   0.4 ms    [User: 48.6 ms, System: 24.7 ms]
      Range (min … max):    73.1 ms …  75.5 ms    200 runs

    Summary
      Perl ran
        7.23 ± 0.13 times faster than Shell

While a sevenfold slowdown is significant, the benefit of not requiring
Perl for a fully-functioning Git installation outweighs waiting a couple
of milliseconds longer during the build process.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/Makefile
Documentation/cmd-list.perl [deleted file]
Documentation/cmd-list.sh [new file with mode: 0755]
Documentation/meson.build
meson.build

index 6485d40f6206938a91fa15b71ee26b2ee0393290..b109d25e9c804d56511da0439e116a9c17e45ee7 100644 (file)
@@ -317,8 +317,8 @@ cmds_txt = cmds-ancillaryinterrogators.adoc \
 
 $(cmds_txt): cmd-list.made
 
-cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
-       $(QUIET_GEN)$(PERL_PATH) ./cmd-list.perl .. . $(cmds_txt) && \
+cmd-list.made: cmd-list.sh ../command-list.txt $(MAN1_TXT)
+       $(QUIET_GEN)$(SHELL_PATH) ./cmd-list.sh .. . $(cmds_txt) && \
        date >$@
 
 mergetools-%.adoc: generate-mergetool-list.sh ../git-mergetool--lib.sh $(wildcard ../mergetools/*)
diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
deleted file mode 100755 (executable)
index 0a0c1b3..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/perl -w
-
-use File::Compare qw(compare);
-
-sub format_one {
-       my ($source_dir, $out, $nameattr) = @_;
-       my ($name, $attr) = @$nameattr;
-       my ($path) = "$source_dir/Documentation/$name.adoc";
-       my ($state, $description);
-       my $mansection;
-       $state = 0;
-       open I, '<', "$path" or die "No such file $path.adoc";
-       while (<I>) {
-               if (/^(?:git|scalar)[a-z0-9-]*\(([0-9])\)$/) {
-                       $mansection = $1;
-                       next;
-               }
-               if (/^NAME$/) {
-                       $state = 1;
-                       next;
-               }
-               if ($state == 1 && /^----$/) {
-                       $state = 2;
-                       next;
-               }
-               next if ($state != 2);
-               chomp;
-               $description = $_;
-               last;
-       }
-       close I;
-       if (!defined $description) {
-               die "No description found in $path.adoc";
-       }
-       if (my ($verify_name, $text) = ($description =~ /^($name) - (.*)/)) {
-               print $out "linkgit:$name\[$mansection\]::\n\t";
-               if ($attr =~ / deprecated /) {
-                       print $out "(deprecated) ";
-               }
-               print $out "$text.\n\n";
-       }
-       else {
-               die "Description does not match $name: $description";
-       }
-}
-
-my ($source_dir, $build_dir, @categories) = @ARGV;
-
-open IN, "<$source_dir/command-list.txt";
-while (<IN>) {
-       last if /^### command list/;
-}
-
-my %cmds = ();
-for (sort <IN>) {
-       next if /^#/;
-
-       chomp;
-       my ($name, $cat, $attr) = /^(\S+)\s+(.*?)(?:\s+(.*))?$/;
-       $attr = '' unless defined $attr;
-       push @{$cmds{$cat}}, [$name, " $attr "];
-}
-close IN;
-
-for my $out (@categories) {
-       my ($cat) = $out =~ /^cmds-(.*)\.adoc$/;
-       my ($path) = "$build_dir/$out";
-       open O, '>', "$path+" or die "Cannot open output file $out+";
-       for (@{$cmds{$cat}}) {
-               format_one($source_dir, \*O, $_);
-       }
-       close O;
-
-       if (-f "$path" && compare("$path", "$path+") == 0) {
-               unlink "$path+";
-       }
-       else {
-               rename "$path+", "$path";
-       }
-}
diff --git a/Documentation/cmd-list.sh b/Documentation/cmd-list.sh
new file mode 100755 (executable)
index 0000000..077def3
--- /dev/null
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+set -e
+
+format_one () {
+       source_dir="$1"
+       command="$2"
+       attributes="$3"
+
+       path="$source_dir/Documentation/$command.adoc"
+       if ! test -f "$path"
+       then
+               echo >&2 "No such file $path"
+               exit 1
+       fi
+
+       state=0
+       while read line
+       do
+               case "$state" in
+               0)
+                       case "$line" in
+                       git*\(*\)|scalar*\(*\))
+                               mansection="${line##*\(}"
+                               mansection="${mansection%\)}"
+                               ;;
+                       NAME)
+                               state=1;;
+                       esac
+                       ;;
+               1)
+                       if test "$line" = "----"
+                       then
+                               state=2
+                       fi
+                       ;;
+               2)
+                       description="$line"
+                       break
+                       ;;
+               esac
+       done <"$path"
+
+       if test -z "$mansection"
+       then
+               echo "No man section found in $path" >&2
+               exit 1
+       fi
+
+       if test -z "$description"
+       then
+               echo >&2 "No description found in $path"
+               exit 1
+       fi
+
+       case "$description" in
+       "$command - "*)
+               text="${description#$command - }"
+
+               printf "linkgit:%s[%s]::\n\t" "$command" "$mansection"
+               case "$attributes" in
+               *" deprecated "*)
+                       printf "(deprecated) "
+                       ;;
+               esac
+               printf "$text.\n\n"
+               ;;
+       *)
+               echo >&2 "Description does not match $command: $description"
+               exit 1
+               ;;
+       esac
+}
+
+source_dir="$1"
+build_dir="$2"
+shift 2
+
+for out
+do
+       category="${out#cmds-}"
+       category="${category%.adoc}"
+       path="$build_dir/$out"
+
+       while read command command_category attributes
+       do
+               case "$command" in
+               "#"*)
+                       continue;;
+               esac
+
+               case "$command_category" in
+               "$category")
+                       format_one "$source_dir" "$command" " $attributes ";;
+               esac
+       done <"$source_dir/command-list.txt" >"$build_dir/$out+"
+
+       if cmp "$build_dir/$out+" "$build_dir/$out" >/dev/null 2>&1
+       then
+               rm "$build_dir/$out+"
+       else
+               mv "$build_dir/$out+" "$build_dir/$out"
+       fi
+done
index 8b9e692c5992afa63d3438a4c5aa9ec9d111ebcb..b731c76e9e773186437e413a2a9a6fbc7ca02f43 100644 (file)
@@ -315,12 +315,12 @@ cmd_lists = [
 
 documentation_deps += custom_target(
   command: [
-    perl,
+    shell,
     '@INPUT@',
     meson.project_source_root(),
     meson.current_build_dir(),
   ] + cmd_lists,
-  input: 'cmd-list.perl',
+  input: 'cmd-list.sh',
   output: cmd_lists
 )
 
index 8bab8f3481fcb16dc4d58d3ba257058c988b09b2..97753d2cfa7d44d2d627e8a3fc54060ed11c0207 100644 (file)
@@ -779,7 +779,7 @@ endif
 # features. It is optional if you want to neither execute tests nor use any of
 # these optional features.
 perl_required = get_option('perl')
-if get_option('gitweb').enabled() or 'netrc' in get_option('credential_helpers') or get_option('docs') != []
+if get_option('gitweb').enabled() or 'netrc' in get_option('credential_helpers')
   perl_required = true
 endif