From 7450a781beeb0ca95eae9baf85dc9708f652b674 Mon Sep 17 00:00:00 2001
From: Jelte Fennema <jelte.fennema@microsoft.com>
Date: Mon, 23 Jan 2023 11:14:09 +0100
Subject: [PATCH v1 2/2] Add pgindent pre-commit hook

It's easy to forget to run pgindent before doing a commit, eventhough
most of the time you will want to do it. This adds an (optional)
pre-commit hook that can run pgindent automatically for you whenever you
commit. To make this pgindent faster to run it also adds flags to
pgindent to only run against staged/changed files instead of against the
whole repo.
---
 src/tools/pgindent/README     | 19 +++++++++++
 src/tools/pgindent/pgindent   | 64 ++++++++++++++++++++++++++++-------
 src/tools/pgindent/pre-commit |  4 +++
 3 files changed, 74 insertions(+), 13 deletions(-)
 create mode 100755 src/tools/pgindent/pre-commit

diff --git a/src/tools/pgindent/README b/src/tools/pgindent/README
index 103970c1042..7b67f30c062 100644
--- a/src/tools/pgindent/README
+++ b/src/tools/pgindent/README
@@ -150,6 +150,9 @@ but we currently exclude *.y and *.l files, as well as *.c and *.h files
 derived from *.y and *.l files.  Additional exceptions are listed
 in exclude_file_patterns; see the notes therein for rationale.
 
+You can choose to only process files that are changed according to git by using
+the --staged-only or --changed-only flags.
+
 Note that we do not exclude ecpg's header files from the run.  Some of them
 get copied verbatim into ecpg's output, meaning that ecpg's expected files
 may need to be updated to match.
@@ -157,3 +160,19 @@ may need to be updated to match.
 The perltidy run processes all *.pl and *.pm files, plus a few
 executable Perl scripts that are not named that way.  See the "find"
 rules in pgperltidy for details.
+
+---------------------------------------------------------------------------
+
+Automation
+----------
+
+You can install the pre-commit hook by running:
+
+	cp src/tools/pgindent/pre-commit .git/hooks/pre-commit
+
+This hook will automatically run pgindent on all of the files you are
+commiting. It will abort the commit if it made any changes, so that you can add
+those changes to your commit. If you did not indent this commit on purpose,
+simply run git commit again and it will succeed. If before committing you
+already know you don't want to run pgindent at all you can use the --no-verify
+flag of git commit to skip the hook.
diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index 741b0ccb586..b6c79b49872 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -21,7 +21,7 @@ my $indent_opts =
 
 my $devnull = File::Spec->devnull;
 
-my ($typedefs_file, $typedef_str, $code_base, $excludes, $indent, $build);
+my ($typedefs_file, $typedef_str, $code_base, $excludes, $indent, $build, $changed_only, $staged_only, $fail_on_changed);
 
 my %options = (
 	"typedefs=s"         => \$typedefs_file,
@@ -29,7 +29,10 @@ my %options = (
 	"code-base=s"        => \$code_base,
 	"excludes=s"         => \$excludes,
 	"indent=s"           => \$indent,
-	"build"              => \$build,);
+	"build"              => \$build,
+	"changed-only"       => \$changed_only,
+	"staged-only"        => \$staged_only,
+	"fail-on-changed"    => \$fail_on_changed);
 GetOptions(%options) || die "bad command line argument\n";
 
 run_build($code_base) if ($build);
@@ -381,17 +384,42 @@ sub build_clean
 # main
 
 # get the list of files under code base, if it's set
-File::Find::find(
+if ($changed_only)
+{
+	my $staged_files = `git diff --name-only --diff-filter=ACMR`;
+	if ($? != 0)
 	{
-		wanted => sub {
-			my ($dev, $ino, $mode, $nlink, $uid, $gid);
-			(($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))
-			  && -f _
-			  && /^.*\.[ch]\z/s
-			  && push(@files, $File::Find::name);
-		}
-	},
-	$code_base) if $code_base;
+		print STDERR
+		  "Failed to get changed files using git.\n";
+		exit 1;
+	}
+	@files = grep(/^.*\.[ch]\z/s, split("\n", $staged_files));
+}
+elsif ($staged_only)
+{
+	my $staged_files = `git diff --staged --name-only --diff-filter=ACMR`;
+	if ($? != 0)
+	{
+		print STDERR
+		  "Failed to get staged files using git.\n";
+		exit 1;
+	}
+	@files = grep(/^.*\.[ch]\z/s, split("\n", $staged_files));
+}
+else
+{
+	File::Find::find(
+		{
+			wanted => sub {
+				my ($dev, $ino, $mode, $nlink, $uid, $gid);
+				(($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))
+				  && -f _
+				  && /^.*\.[ch]\z/s
+				  && push(@files, $File::Find::name);
+			}
+		},
+		$code_base) if $code_base;
+}
 
 process_exclude();
 
@@ -402,6 +430,8 @@ check_indent();
 # any non-option arguments are files to be processed
 push(@files, @ARGV);
 
+my $changed_any_files = 0;
+
 foreach my $source_filename (@files)
 {
 
@@ -429,7 +459,15 @@ foreach my $source_filename (@files)
 
 	$source = post_indent($source, $source_filename);
 
-	write_source($source, $source_filename) if $source ne $orig_source;
+	if ($source ne $orig_source) {
+		write_source($source, $source_filename);
+		$changed_any_files = 1;
+	}
 }
 
 build_clean($code_base) if $build;
+if ($fail_on_changed && $changed_any_files)
+{
+	print STDERR "ERROR: Some of your files required formatting changes according to pgindent.\n";
+	exit(1)
+}
diff --git a/src/tools/pgindent/pre-commit b/src/tools/pgindent/pre-commit
new file mode 100755
index 00000000000..b0f2347ebfd
--- /dev/null
+++ b/src/tools/pgindent/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+set -eu
+
+src/tools/pgindent/pgindent --staged-only --fail-on-changed
-- 
2.34.1

