

=head1 NAME

Directory::Diff - recursively find differences between similar directories

=head1 SYNOPSIS

    
    use Directory::Diff 'directory_diff';
    use FindBin '$Bin';
    
    # Do a "diff" between "old_dir" and "new_dir"
    
    directory_diff ("$Bin/old_dir", "$Bin/new_dir", 
                    {diff => \& diff,
                     dir1_only => \& old_only});
    
    # User-supplied callback for differing files
    
    sub diff
    {
        my ($data, $dir1, $dir2, $file) = @_;
        print "$dir1/$file is different from $dir2/$file.\n";
    }
    
    # User-supplied callback for files only in one of the directories
    
    sub old_only
    {
        my ($data, $dir1, $file) = @_;
        print "$dir1/$file is only in the old directory.\n";
    }
    


produces output

    /usr/home/ben/projects/directory-diff/examples/old_dir/old-file is only in the old directory.
    /usr/home/ben/projects/directory-diff/examples/old_dir/diff-file is different from /usr/home/ben/projects/directory-diff/examples/new_dir/diff-file.


(This example is included as L<F<synopsis.pl>|https://fastapi.metacpan.org/source/BKB/HTML-Make-0.09/examples/synopsis.pl> in the distribution.)  

=head1 VERSION

This documents Directory-Diff version 0.09
corresponding to git commit L<6985ccf7389d41b632e46843b493cc2626d2a881|https://github.com/benkasminbullock/Directory-Diff/commit/6985ccf7389d41b632e46843b493cc2626d2a881> released on Tue May 13 08:25:57 2025 +0900.

=head1 DESCRIPTION

Directory::Diff finds differences between two directories and all
their subdirectories, recursively. If it finds a file with the same
name in both directories, it uses L<File::Compare> to find out whether
they are different. It is callback-based and takes actions only if
required.

=head1 FUNCTIONS

The main function of this module is L</directory_diff>. The other
functions listed here are helper functions, but these can be exported
on request.

=head2 directory_diff

     directory_diff ("dir1", "dir2", 
                     {dir1_only => \&dir1_only,
                      diff => \& diff});

Given two directories F<dir1> and F<dir2>, this calls back a
user-supplied routine for each of three cases:

=over

=item A file is only in the first directory

In this case a callback specified by C<dir1_only> is called once

     &{$third_arg->{dir1_only}} ($third_arg->{data}, "dir1", $file);

for each file C<$file> which is in F<dir1> but not in F<dir2>,
including files in subdirectories.

=item A file is only in the second directory

In this case a callback specified by C<dir2_only> is called once

     &{$third_arg->{dir2_only}} ($third_arg->{data}, "dir2", $file);

for each file C<$file> which is in F<dir2> but not in F<dir1>,
including files in subdirectories.

=item A file with the same name but different contents is in both directories

In this case a callback specified by C<diff> is called once

     &{$third_arg->{diff}} ($third_arg->{data}, "dir1", "dir2", $file);

for each file name C<$file> which is in both F<dir1> and in F<dir2>,
including files in subdirectories.

=back

The first argument to each of the callback functions is specified by
C<data>. The second argument to C<dir1_only> and C<dir2_only> is the
directory's name. The third argument is the file name, which includes
the subdirectory part. The second and third arguments to C<diff> are
the two directories, and the fourth argument is the file name
including the subdirectory part.

If the user does not supply a callback, no action is taken, even if a
file is found.

The routine does not return a meaningful value. It does not check the
return values of the callbacks. Therefore if it is necessary to stop
midway, the user must use something like C<eval { }> and C<die>.

A fourth argument, if set to any true value, causes directory_diff to
print messages about what it finds and what it does.

=head2 ls_dir

     my %ls = ls_dir ("dir");

C<ls_dir> makes a hash containing a true value for each file and
directory which is found under the directory given as the first
argument.

If a second argument with a true value is set, it prints debugging
messages. For example

     my %ls = ls_dir ("dir", 1);

As a debugging measure it halts execution if its return value is
ignored.

=head2 get_only

     my %only = get_only (\%dir1, \%dir2);

Given two hashes containing true values for each file or directory
under two directories, return a hash containing true values for the
files and directories which are in the first directory hash but not in
the second directory hash.

For example, if

     %dir1 = ("file" => 1, "dir/" => 1, "dir/file" => 1);

and 

     %dir2 = ("dir/" => 1, "dir2/" => 1);

C<get_only> returns

     %only = ("file" => 1, "dir/file" => 1);

A third parameter for debugging makes the module print messages
on what is found if set to a true value, for example,

     my %only = get_only (\%dir1, \%dir2, 1);

=head2 get_diff

     my %diff = get_diff ("dir1", \%dir1_ls, "dir2", \%dir2_ls);

Get a list of files which are in both C<dir1> and C<dir2>, but which
are different. This uses L<File::Compare> to test the files for
differences. It searches subdirectories. Usually the hashes
C<%dir1_ls> and C<%dir2_ls> are those output by L</ls_dir>.

=head1 SEE ALSO

=head2 CPAN modules

=over

=item L<File::DirCompare>

This is similar to Directory::Diff.  Unlike Directory::Diff, it does
not descend into subdirectories which exist in one directory but not
the other.

=item L<File::Dircmp>

This mimics the output of the Unix C<diff> command.  Unlike
Directory::Diff, it does not descend into subdirectories which exist
in one directory but not the other.

=item L<Test::Dirs>

=item L<Compare::Directory>

=back

=head1 DEPENDENCIES

This section lists Perl modules which this depends on, with a
rationale for why they are used.

=over

=item L<Carp>

C<croak> and C<carp> are used to report errors.

=item L<File::Compare>

File::Compare is used to check whether two identically-named files are
different or not.

=item L<Cwd/getcwd>

This is used to get the working directory of the module, since it
changes directory to the directory where the diff is performed.

=item L<File::Copy>

See L<Directory::Diff::Copy>.

=item L<File::Path>

See L<Directory::Diff::Copy>.

=back

=head1 CONTRIBUTORS

This section lists people who have contributed to the module.

Mohammad S. Anwar (MANWAR) contributed fixes for broken tests.

Slaven Rezic (ESERTE) contributed bug fixes and suggestions for improvement.

=head1 MOTIVATION

This section discusses why I wrote the module and what I use it for.

The reason I wrote this module is because C<< `diff --recursive` >>
stops when it finds a subdirectory which is in one directory and not
the other, without descending into the subdirectory. For example, if
one has a file like C<dir1/subdir/file>,

     diff -r dir1 dir2

will tell you "Only in dir1: subdir" but it won't tell you anything
about the files under "subdir". The two Perl modules on CPAN,
L</File::Dircmp> and L</File::DirCompare> both also stop processing
when subdirectories differ.

For my task, I needed to go down into the subdirectory and find all
the files which were in all the subdirectories, so I wrote this.

I've been using this module for updating web sites with a lot of pages
since 2009, to avoid repeatedly having to upload the entire
site's-worth of pages for each small change. The way I use this is as
follows. I keep a local copy of the uploaded web site in a directory
like F<old-site>, and then rebuild all the pages in another directory
like F<new-site>, then I use L<Directory::Diff::Copy> to put the
changed files into yet another directory, like
F<changed-site-files>. Once the changed files are copied, then I tar,
gzip, and upload the directory of changed files, and untar it at the
web host, thus replacing only files which have changed. I also delete
the F<old-site> directory and rename F<new-site> to F<old-site> at
this point in preparation for the next upload.

I'm currently using this for almost all the static content for the
following web sites: L<http://www.sljfaq.org>,
L<http://kanji.sljfaq.org>, and L<http://www.lemoda.net>. I put this
module on github in about 2012 and on CPAN in 2016.



=head1 AUTHOR

Ben Bullock, <benkasminbullock@gmail.com>

=head1 COPYRIGHT & LICENCE

This package and associated files are copyright (C) 
2009-2025
Ben Bullock.

You can use, copy, modify and redistribute this package and associated
files under the Perl Artistic Licence or the GNU General Public
Licence.



