A problem I'm having is that if I'm having a build directory inside the
source tree
meson setup build
(as is documented) and then run pgindent
src/tools/pgindent/pgindent .
it descends into the build directory and then chokes on various files in
there. (exclude_file_patterns doesn't help because those patterns are
applied relative to the source directory.)
A subproblem is that if pgindent fails on many files, it doesn't clean
up after itself but leaves many pgsrc*.BAK files lying around. The
first patch fixes that.
Then, my first idea was to add an option to pgindent to exclude
directories from being processed. Like
src/tools/pgindent/pgindent . --exclude-dir=build
The second patch implements that.
But then I had the idea that we could recognize build directories
automatically by checking what files they contain. The third patch
implements that. That way, my initial pgindent invocation above works
out of the box and I don't have to remember to enumerate the build
directories on the command line.
(So the second patch is maybe obsolete, but the third one builds on it,
so I left it in here for discussion. People seem to have a variety of
workflows, so let's see what everyone wants.)
From 7fd2f1ee7400994de4387eea11478fff8c2270e3 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Thu, 24 Sep 2026 12:10:00 +0200
Subject: [PATCH 1/3] pgindent: Don't leave a .BAK file behind when
pg_bsd_indent fails
run_indent() returned early when pg_bsd_indent reported an error,
before unlinking the .BAK file that pg_bsd_indent writes. The
existing END block cleaned up only the most recent one, but if
pg_bsd_indent fails on many files, it littered the current directory
with pgsrc*.BAK files. To fix, remove the backup file after the
pg_bsd_indent run whether or not the run succeeded.
---
src/tools/pgindent/pgindent | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index eea6c0ad734..d25f547e289 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -373,12 +373,15 @@ sub run_indent
$bak_to_cleanup = "$filename.BAK";
$$error_message = `$cmd $filename 2>&1`;
+ my $rc = $?;
- return "" if ($? || length($$error_message) > 0);
-
+ # pg_bsd_indent leaves its .BAK file behind whether or not it succeeded,
+ # so get rid of it before looking at the result.
unlink $bak_to_cleanup;
$bak_to_cleanup = undef;
+ return "" if ($rc || length($$error_message) > 0);
+
open(my $src_out, '<', $filename) || die $!;
local ($/) = undef;
$source = <$src_out>;
base-commit: 4545cee303c257e58195e3d033c05bf38e2cd4d6
--
2.55.0
From c3c055b114ecb6ddbc84e4f338b78c411b0a7bdf Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Thu, 24 Sep 2026 11:50:00 +0200
Subject: [PATCH 2/3] pgindent: Add --exclude-dir option
This is meant to exclude build directories from being processed by
pgindent, which is both wasteful and likely to crash pgindent.
---
src/tools/pgindent/pgindent | 57 ++++++++++++++++++++++++++++++---
src/tools/pgindent/pgindent.man | 9 ++++++
2 files changed, 62 insertions(+), 4 deletions(-)
diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index d25f547e289..cbfa50fadb0 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -43,7 +43,8 @@ my $indent_opts =
my $devnull = File::Spec->devnull;
-my ($typedefs_file, $typedef_str, @excludes, $indent, $diff,
+my ($typedefs_file, $typedef_str, @excludes,
+ @exclude_dirs, $indent, $diff,
$check, $help, @commits,);
$help = 0;
@@ -54,6 +55,7 @@ my %options = (
"typedefs=s" => \$typedefs_file,
"list-of-typedefs=s" => \$typedef_str,
"excludes=s" => \@excludes,
+ "exclude-dir=s" => \@exclude_dirs,
"indent=s" => \$indent,
"diff" => \$diff,
"check" => \$check,);
@@ -64,6 +66,12 @@ usage() if $help;
usage("Cannot use --commit with command line file list")
if (@commits && @ARGV);
+# --exclude-dir arguments are relative to the directory pgindent was
+# invoked in, so resolve them to absolute paths now, before we
+# possibly chdir() elsewhere. abs_path returns undef for a directory
+# that doesn't exist, so drop those.
+@exclude_dirs = grep { defined } map { abs_path($_) } @exclude_dirs;
+
# command line option wins, then environment, then locations based on current
# dir, then default location
$typedefs_file ||= $ENV{PGTYPEDEFS};
@@ -209,6 +217,32 @@ sub process_exclude
return;
}
+# Is the given file or directory inside one of the --exclude-dir
+# directories? The argument is interpreted relative to the current
+# directory.
+sub in_excluded_dir
+{
+ my $path = shift;
+
+ return 0 unless @exclude_dirs;
+
+ # abs_path() returns undef if a directory along the path is
+ # missing, which can happen for a file named by --commit that has
+ # since been removed. Fall back to a purely textual absolute
+ # path, so that such a file is still recognized as excluded rather
+ # than warned about later.
+ my $abs = abs_path($path);
+ $abs = File::Spec->canonpath(File::Spec->rel2abs($path))
+ unless defined $abs;
+
+ foreach my $dir (@exclude_dirs)
+ {
+ return 1 if $abs eq $dir || index($abs, "$dir/") == 0;
+ }
+
+ return 0;
+}
+
sub read_source
{
my $source_filename = shift;
@@ -418,10 +452,12 @@ Options:
--typedefs=FILE file containing a list of typedefs
--list-of-typedefs=STR string containing typedefs, space separated
--excludes=PATH file containing list of filename patterns to
ignore
+ --exclude-dir=DIR skip all files below the directory DIR
--indent=PATH path to pg_bsd_indent program
--diff show the changes that would be made
--check exit with status 2 if any changes would be made
-The --excludes and --commit options can be given more than once.
+The --excludes, --exclude-dir, and --commit options can be given more
+than once.
EOF
if ($help)
{
@@ -443,8 +479,17 @@ check_indent();
my $wanted = sub {
my ($dev, $ino, $mode, $nlink, $uid, $gid);
- (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))
- && -f _
+ return unless (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_));
+
+ # Don't even descend into an excluded directory. Note that File::Find
+ # has chdir'd into the containing directory, so $_ is what we must test.
+ if (-d _ && in_excluded_dir($_))
+ {
+ $File::Find::prune = 1;
+ return;
+ }
+
+ -f _
&& /^.*\.[ch]\z/s
&& push(@files, $File::Find::name);
};
@@ -465,6 +510,10 @@ foreach my $commit (@commits)
push(@files, @affected);
}
+# remove files below excluded directories from the file list (could be
+# from command line or from --commit)
+@files = grep { !in_excluded_dir($_) } @files if @exclude_dirs;
+
warn "No files to process" unless @files;
# remove excluded files from the file list
diff --git a/src/tools/pgindent/pgindent.man b/src/tools/pgindent/pgindent.man
index caab5cde914..cac2d30cd41 100644
--- a/src/tools/pgindent/pgindent.man
+++ b/src/tools/pgindent/pgindent.man
@@ -31,6 +31,15 @@ find the file src/tools/pgindent/exclude_file_patterns. The
--excludes option
can be used more than once to specify multiple files containing exclusion
patterns.
+Whole directories can be skipped with the --exclude-dir option, which is
+useful for build directories containing generated .c and .h files, for
+example:
+
+ pgindent . --exclude-dir=build --exclude-dir=build2
+
+Relative directory names are interpreted relative to the directory pgindent
+is invoked in. This option can also be used more than once.
+
There are also two non-destructive modes of pgindent. If given the --diff
option pgindent will show the changes it would make, but doesn't actually make
them. If given instead the --check option, pgindent will exit with a status of
--
2.55.0
From b2b690df0207e887722166b2796676cf93dd1ff0 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Thu, 24 Sep 2026 16:03:14 +0200
Subject: [PATCH 3/3] pgindent: Skip build directories automatically
pgindent descended into build directories located inside the source
tree and reindented the generated files there, which is wasteful and
often makes pg_bsd_indent fail outright. The --exclude-dir option
added by the previous commit can take care of that, but it has to be
spelled out on every invocation, and the names of the build
directories are a local choice that pgindent cannot guess.
This change automatically recognizes build directories and skips them.
Meson build directories are identified by the presence of
meson-private/coredata.dat, which is the same test Meson itself uses;
Autoconf build directories created outside the source tree are
identified by config.status without configure.ac. A build directory
named directly on the command line is still processed, on the
assumption that this was asked for on purpose.
---
src/tools/pgindent/pgindent | 27 +++++++++++++++++++++++----
src/tools/pgindent/pgindent.man | 8 ++++++++
2 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/src/tools/pgindent/pgindent b/src/tools/pgindent/pgindent
index cbfa50fadb0..fceeeeb2559 100755
--- a/src/tools/pgindent/pgindent
+++ b/src/tools/pgindent/pgindent
@@ -243,6 +243,20 @@ sub in_excluded_dir
return 0;
}
+# Does the given directory look like the top of a build tree?
+sub is_build_dir
+{
+ my $dir = shift;
+
+ # This is how Meson itself identifiers a build tree.
+ return 1 if -f "$dir/meson-private/coredata.dat";
+
+ # Autoconf VPATH build directory
+ return 1 if -f "$dir/config.status" && !-f "$dir/configure.ac";
+
+ return 0;
+}
+
sub read_source
{
my $source_filename = shift;
@@ -481,11 +495,16 @@ my $wanted = sub {
my ($dev, $ino, $mode, $nlink, $uid, $gid);
return unless (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_));
- # Don't even descend into an excluded directory. Note that File::Find
- # has chdir'd into the containing directory, so $_ is what we must test.
- if (-d _ && in_excluded_dir($_))
+ # Don't even descend into an excluded directory, nor into a build tree.
+ #
+ # Note that File::Find has chdir'd into the containing directory,
+ # so $_ is what we must test; for a directory named on the command
+ # line $_ is ".", and we process such a directory even if it is a
+ # build tree, since that was asked for explicitly.
+ if (-d _)
{
- $File::Find::prune = 1;
+ $File::Find::prune = 1
+ if in_excluded_dir($_) || ($_ ne "." && is_build_dir($_));
return;
}
diff --git a/src/tools/pgindent/pgindent.man b/src/tools/pgindent/pgindent.man
index cac2d30cd41..6e6d193561d 100644
--- a/src/tools/pgindent/pgindent.man
+++ b/src/tools/pgindent/pgindent.man
@@ -40,6 +40,14 @@ example:
Relative directory names are interpreted relative to the directory pgindent
is invoked in. This option can also be used more than once.
+Build directories that pgindent comes across while scanning a
+directory are recognized and skipped automatically, so they usually
+don't need to be listed with --exclude-dir. This works for Meson
+build directories and for Autoconf build directories created outside
+the source tree. A build directory named directly on the command line
+is still processed, on the assumption that this was asked for on
+purpose.
+
There are also two non-destructive modes of pgindent. If given the --diff
option pgindent will show the changes it would make, but doesn't actually make
them. If given instead the --check option, pgindent will exit with a status of
--
2.55.0