From f6d855091e73fdab8a39185a8392b9d0df7ed46f Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 16 Apr 2025 14:16:07 +0200 Subject: [PATCH 1/4] filter-branch: stop depending on Perl While git-filter-branch(1) is written as a shell script, the `--state-branch` feature depends on Perl to save and extract the object ID mappings. This can lead to subtle breakage though: - We execute `perl` directly without respecting the `PERL_PATH` configured by the distribution. As such, it may happen that we use the wrong version of Perl. - We install the script unchanged even if Perl isn't available at all on the system, so using `--state-branch` would lead to failure altogether in that case. Fix this by dropping Perl and instead implementing the feature with shell scripting exclusively. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- git-filter-branch.sh | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 3a51d4507c..24fa317aaa 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -295,15 +295,18 @@ then if test -n "$state_commit" then echo "Populating map from $state_branch ($state_commit)" 1>&2 - perl -e'open(MAP, "-|", "git show $ARGV[0]:filter.map") or die; - while () { - m/(.*):(.*)/ or die; - open F, ">../map/$1" or die; - print F "$2" or die; - close(F) or die; - } - close(MAP) or die;' "$state_commit" \ - || die "Unable to load state from $state_branch:filter.map" + + git show "$state_commit:filter.map" >"$tempdir"/filter-map || + die "Unable to load state from $state_branch:filter.map" + while read line + do + case "$line" in + *:*) + echo "${line%:*}" >../map/"${line#*:}";; + *) + die "Unable to load state from $state_branch:filter.map";; + esac + done <"$tempdir"/filter-map else echo "Branch $state_branch does not exist. Will create" 1>&2 fi @@ -633,15 +636,13 @@ if test -n "$state_branch" then echo "Saving rewrite state to $state_branch" 1>&2 state_blob=$( - perl -e'opendir D, "../map" or die; - open H, "|-", "git hash-object -w --stdin" or die; - foreach (sort readdir(D)) { - next if m/^\.\.?$/; - open F, "<../map/$_" or die; - chomp($f = ); - print H "$_:$f\n" or die; - } - close(H) or die;' || die "Unable to save state") + for file in ../map/* + do + from_commit=$(basename "$file") + to_commit=$(cat "$file") + echo "$from_commit:$to_commit" + done | git hash-object -w --stdin || die "Unable to save state" + ) state_tree=$(printf '100644 blob %s\tfilter.map\n' "$state_blob" | git mktree) if test -n "$state_commit" then From 76042228f20bd9b97a7454b62c70b28117c351c0 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 16 Apr 2025 14:16:08 +0200 Subject: [PATCH 2/4] request-pull: stop depending on Perl While git-request-pull(1) is written as a shell script, for it to function we depend on Perl being available. The script gets installed unconditionally though, regardless of whether or not Perl is even available on the system. When it's not available, the `@PERL_PATH@` variable may be substituted with a nonexistent executable path and thus cause the script to fail. Refactor the script so that it does not depend on Perl at all anymore. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- git-request-pull.sh | 74 ++++++++++++++++++++++------------------- t/t5150-request-pull.sh | 6 ---- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/git-request-pull.sh b/git-request-pull.sh index 775ba8ea11..6a7b793678 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -78,41 +78,47 @@ fi merge_base=$(git merge-base $baserev $headrev) || die "fatal: No commits in common between $base and $head" -# $head is the refname from the command line. -# Find a ref with the same name as $head that exists at the remote +find_matching_ref () { + while read sha1 ref + do + case "$ref" in + *"^"?*) + ref="${ref%"^"*}" + deref=true + ;; + *) + deref= + ;; + esac + + if test "$sha1" = "${remote:-HEAD}" + then + echo "$sha1 $sha1" + break + fi + + case "$ref" in + "${remote:-HEAD}"|*"/${remote:-HEAD}") + if test -z "$deref" + then + # Remember the matching unpeeled object on the + # remote side. + remote_sha1="$sha1" + fi + + if test "$sha1" = "$headrev" + then + echo "${remote_sha1:-$headrev} $ref" + break + fi + ;; + esac + done +} + +# Find a ref with the same name as $remote that exists at the remote # and points to the same commit as the local object. -find_matching_ref=' - my ($head,$headrev) = (@ARGV); - my $pattern = qr{/\Q$head\E$}; - my ($remote_sha1, $found); - - while () { - chomp; - my ($sha1, $ref, $deref) = /^(\S+)\s+([^^]+)(\S*)$/; - - if ($sha1 eq $head) { - $found = $remote_sha1 = $sha1; - break; - } - - if ($ref eq $head || $ref =~ $pattern) { - if ($deref eq "") { - # Remember the matching object on the remote side - $remote_sha1 = $sha1; - } - if ($sha1 eq $headrev) { - $found = $ref; - break; - } - } - } - if ($found) { - $remote_sha1 = $headrev if ! defined $remote_sha1; - print "$remote_sha1 $found\n"; - } -' - -set fnord $(git ls-remote "$url" | @PERL_PATH@ -e "$find_matching_ref" "${remote:-HEAD}" "$headrev") +set fnord $(git ls-remote "$url" | find_matching_ref) remote_sha1=$2 ref=$3 diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh index cb67bac1c4..270ce6ea48 100755 --- a/t/t5150-request-pull.sh +++ b/t/t5150-request-pull.sh @@ -7,12 +7,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh -if ! test_have_prereq PERL -then - skip_all='skipping request-pull tests, perl not available' - test_done -fi - test_expect_success 'setup' ' git init --bare upstream.git && From 521c98840b6ce5503f771600493f522be3677232 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 16 Apr 2025 14:16:09 +0200 Subject: [PATCH 3/4] Documentation: stop depending on Perl to massage user manual The "fix-texi.perl" script is used to fix up the output of `docbook2x-texi`: - It changes the filename to be "git.info". - It changes the directory category and entry. The script is written in Perl, but it can be rather trivially converted to a shell script. Do so to remove the dependency on Perl for building the user manual. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- Documentation/Makefile | 4 ++-- Documentation/fix-texi.perl | 15 --------------- Documentation/fix-texi.sh | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+), 17 deletions(-) delete mode 100755 Documentation/fix-texi.perl create mode 100755 Documentation/fix-texi.sh diff --git a/Documentation/Makefile b/Documentation/Makefile index 0d3a2c6bfe..6485d40f62 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -398,9 +398,9 @@ user-manual.html: user-manual.xml $(XSLT) git.info: user-manual.texi $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi -user-manual.texi: user-manual.xml +user-manual.texi: user-manual.xml fix-texi.sh $(QUIET_DB2TEXI)$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@+ && \ - $(PERL_PATH) fix-texi.perl <$@+ >$@ && \ + $(SHELL_PATH) fix-texi.sh <$@+ >$@ && \ $(RM) $@+ user-manual.pdf: user-manual.xml diff --git a/Documentation/fix-texi.perl b/Documentation/fix-texi.perl deleted file mode 100755 index ff7d78f620..0000000000 --- a/Documentation/fix-texi.perl +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/perl -w - -while (<>) { - if (/^\@setfilename/) { - $_ = "\@setfilename git.info\n"; - } elsif (/^\@direntry/) { - print '@dircategory Development -@direntry -* Git: (git). A fast distributed revision control system -@end direntry -'; } - unless (/^\@direntry/../^\@end direntry/) { - print; - } -} diff --git a/Documentation/fix-texi.sh b/Documentation/fix-texi.sh new file mode 100755 index 0000000000..bc300f7b0f --- /dev/null +++ b/Documentation/fix-texi.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +awk ' + /^@setfilename/{ + print "@setfilename git.info" + next + } + /^@direntry/{ + direntry=1 + print "@dircategory Development" + print "@direntry" + print "* Git: (git). A fast distributed revision control system" + print "@end direntry" + next + } + /^@end direntry/{ + direntry=0 + next + } + !direntry +' From a7fa5b2f0ccb567a5a6afedece113f207902fa6f Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 16 Apr 2025 14:16:10 +0200 Subject: [PATCH 4/4] Documentation: stop depending on Perl to generate command list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Junio C Hamano --- Documentation/Makefile | 4 +- Documentation/cmd-list.perl | 80 --------------------------- Documentation/cmd-list.sh | 104 ++++++++++++++++++++++++++++++++++++ Documentation/meson.build | 4 +- meson.build | 2 +- 5 files changed, 109 insertions(+), 85 deletions(-) delete mode 100755 Documentation/cmd-list.perl create mode 100755 Documentation/cmd-list.sh diff --git a/Documentation/Makefile b/Documentation/Makefile index 6485d40f62..b109d25e9c 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -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 index 0a0c1b3f61..0000000000 --- a/Documentation/cmd-list.perl +++ /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 () { - 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 () { - last if /^### command list/; -} - -my %cmds = (); -for (sort ) { - 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 index 0000000000..077def3b72 --- /dev/null +++ b/Documentation/cmd-list.sh @@ -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 diff --git a/Documentation/meson.build b/Documentation/meson.build index 8b9e692c59..b731c76e9e 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -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 ) diff --git a/meson.build b/meson.build index 8bab8f3481..97753d2cfa 100644 --- a/meson.build +++ b/meson.build @@ -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