#!/usr/bin/perl -w
#
# srctool -- Tool for managing source packages and modules
# 
# Copyright (C) 2001, Michael Jennings
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies of the Software, its documentation and marketing & publicity
# materials, and acknowledgment shall be given in the documentation, materials
# and software packages that this Software was used.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# $Id: srctool,v 1.62 2006/09/26 20:13:30 mej Exp $
#

use strict;
use POSIX;
use File::Find;
use File::Copy ('&cp', '&mv');

# Mezzanine modules
use Mezzanine::Util;
use Mezzanine::SCM;
use Mezzanine::PkgVars;
use Mezzanine::RPM;
use Mezzanine::Src;
use Mezzanine::Pkg;
use Mezzanine::Config;

my $config;
my $scm;
my $import_as_pdr = 0;
my $local_mode = 0;
my $command = "";

# Print usage information
sub
print_usage_info
{
    my ($leader, $underbar);

    print "\n";
    $leader = "$PROGNAME $VERSION Usage Information";
    $underbar = $leader;
    $underbar =~ s/./-/g;
    print "$leader\n$underbar\n";
    print "\n";
    print "  Syntax:   srctool [ options ] [ <pkg> ]\n";
    print "\n";
    print "    -h --help                        Show this usage information\n";
    print "    -d --debug                       Turn on debugging\n";
    print "    -v --version                     Show version and copyright\n";
    print "    -i --import                      Import a new package/source tree\n";
    print "    -p --prepare                     Prepare a working tree\n";
    print "    -m --merge                       Merge one or more raw files into an SPM\n";
    print "    -a --apply --patch               Apply changes in the working tree as a patch to the SPM\n";
    print "    -c --clean                       Clean up a package tree\n";
    print "    -r --resync                      Prepare to sync the repository with the current sources\n";
    print "    -n --name <name>                 Specify the name of a package (-i) or patch (-a)\n";
    print "    -D --dir <repository>            Specify the repository to use\n";
    print "    -L --local                       Local mode; do not talk to the master server\n";
    print "    -f --flat --pdr                  Import as PDR (all files in top dir) instead of SPM\n";
    print "    -R --path --relpath <repo path>  Import into repository at specified path\n";
    print "    -P --protocol <proto>            SCM protocol to use (e.g., \"cvs\", \"svn\")\n";
    print "    -k --keep <type>                 Preserve files by type (merge/sync, types F,P,S)\n";
    print "       --move                        Move/rename file(s)\n";
    print "       --savecfg                     Preserve current settings for future use\n";
    print "\n";
    exit(MEZZANINE_SUCCESS);
}

# Download any URL's we were given as files.
sub
download_files(@)
{
    my @flist = @_;

    for (my $i = 0; $i < scalar(@flist); $i++) {
        my $fname = $flist[$i];
        my $ftype = '';

        if ($fname =~ /^([SPRF]:)(.*)$/) {
            ($ftype, $fname) = ($1, $2);
        }
        if ((! -e $fname) || (index($fname, "://") >= 0)) {
            my $tmp;

            # It's a URL, so download it.
            print "Downloading $fname...";
            $tmp = &fetch_url($fname);
            if (-e $tmp) {
                $flist[$i] = "$ftype$tmp";
            } else {
                eprint "Unable to fetch $fname -- $tmp.\n";
                splice(@flist, $i, 1);
                $i--;
            }
        }
    }
    return @flist;
}

# Import a package
sub
import_package()
{
    my ($pkgfile, $name, $err, $msg, $cmd, $pwd, $tmpdir, $rpmcmd, $pkg, $ver, $rel, $arch, $spec, $tag, $specdata);
    my (@contents, @srcs, @patches, @tmp);

    dprint &print_args(@_);
    $pwd = &getcwd();
    dprint "Working directory is $pwd.\n";
    $pkgfile = &pkgvar_filename();
    $name = &pkgvar_name();

    # Create a working tree to be imported
    if ($pkgfile && -f $pkgfile) {
        ($pkg, $ver, $rel, $arch) = &parse_rpm_name($pkgfile);
        if ($arch !~ /^(no)?src$/) {
            eprint "Invalid package for import:  $pkgfile\n";
            return MEZZANINE_INVALID_PACKAGE;
        }

        print "Importing $pkgfile into $pkg tree....\n\n";

        # Create temp space for importing.
        if ($import_as_pdr) {
            $tmpdir = &create_temp_space($pkg, "PDR");
            if (! $tmpdir) {
                return MEZZANINE_SYSTEM_ERROR;
            }
            &convert_srpm_to_pdr($pkgfile, $tmpdir);
        } else {
            $tmpdir = &create_temp_space($pkg, "SPM");
            if (! $tmpdir) {
                return MEZZANINE_SYSTEM_ERROR;
            }
            &convert_srpm_to_spm($pkgfile, $tmpdir);
        }

        $tag = "$pkg-$ver-$rel";
        if ($local_mode) {
            if (! &copy_tree($tmpdir, "$pwd/$pkg")) {
                eprint "Unable to move $tmpdir to $pwd/$pkg -- $!\n";
                &nuke_tree("$pwd/$pkg");
                return MEZZANINE_FILE_OP_FAILED;
            } else {
                &nuke_tree($tmpdir);
            }
            print "You requested local mode.  To add this tree to SCM, you will need to import it by hand (mzimport $pkg).\n";
        } else {
            my $module;
            my $tmp;

            $tag =~ tr/a-z/A-Z/;
            $tag =~ s/\./_/g;
            $scm->scmobj_propset("target_tag", $tag);
            ($tag = $pkg) =~ tr/a-z/A-Z/;
            $scm->scmobj_propset("source_tag", $tag);
            $scm->scmobj_propset("use_standard_ignore", 0);

            chdir($pwd);
            if ($OPTION{"path"} && -d $OPTION{"path"}) {
                # Relative path exists, so parse in terms of
                # the current directory and SCM context.
                chdir($OPTION{"path"});
                $tmp = $scm->relative_path();
            } elsif (! $OPTION{"path"}) {
                # No path.  Check for relative path in current repo.
                $tmp = $scm->relative_path();
            } else {
                # Supplied path doesn't exist, so take it as gospel.
                $tmp = $OPTION{"path"};
            }
            if ($tmp && (length($tmp) > 1)) {
                $module = "$tmp/$pkg";
            } else {
                $module = $pkg;
            }

            if (! $scm->scmobj_propget("repository")) {
                $scm->detect_repository();
            }

            chdir($tmpdir);
            if (! -f "ChangeLog") {
                my ($tmpfile, $msg);

                $msg = sprintf("Mezzanine auto-imported %s from %s.",
                               $pkg, &basename($pkgfile));
                #$tmpfile = &do_changelog_entry(1, $msg);
                if ($tmpfile) {
                    unlink($tmpfile);
                }
            }
            $err = $scm->imprt($module);

            if ($err != MEZZANINE_SUCCESS) {
                eprint "Import of $pkgfile failed.\n";
                return $err;
            }
            $scm->scmobj_propset("source_tag", "");
            $scm->scmobj_propset("target_tag", "");
            chdir($pwd);
        }
        if ($tmpdir) {
            &clean_temp_space($tmpdir);
        }
    } elsif ((! $pkgfile) || (-d $pkgfile)) {
        dprint "Importing FST.\n";
        if ($pkgfile) {
            $pkg = &basename($pkgfile);
        } elsif (! $pkg) {
            $pkg = &basename(&getcwd());
        }
        if (! $name || $name !~ /-/) {
            if (-t STDIN) {
                my ($pname, $pver);

                print "Missing -n option.  Please supply the following information:\n";
                print "Package name:  ";
                chomp($pname = <STDIN>);
                while (! $pver) {
                    print "Package version:  ";
                    chomp($pver = <STDIN>);
                    if ($pver =~ /-/) {
                        eprint "Package versions cannot contain hyphens.  Please try again.\n";
                        undef $pver;
                    }
                }
                $name = "$pname-$pver";
                print "For future reference, you can specify this information on the command line like this:  -n $name\n";
            } else {
                eprint "No package name/version supplied for FST import\n";
                return MEZZANINE_SYNTAX_ERROR;
            }
        }

        if ($local_mode) {
            printf("You requested local mode.  To add this tree to SCM, you will need to import it by hand%s.\n",
                   (($name) ? (" (mzimport -n $name)") : ("")));
        } else {
            my $module;
            my $tmp;

            ($tag = $name) =~ tr/a-z/A-Z/;
            $tag =~ s/\./_/g;
            $scm->scmobj_propset("target_tag", $tag);
            $tag =~ s/-[^-]*$//;
            $scm->scmobj_propset("source_tag", $tag);
            $scm->scmobj_propset("use_standard_ignore", 0);

            if ($OPTION{"path"} && -d "$pwd/$OPTION{path}") {
                # Relative path exists, so parse in terms of
                # the current directory and SCM context.
                chdir("$pwd/$OPTION{path}");
                $tmp = $scm->relative_path();
            } elsif (! $OPTION{"path"}) {
                # No path.  Check for relative path in current repo.
                $tmp = $scm->relative_path();
                if (!defined($tmp)) {
                    $tmp = $scm->relative_path("..");
                    if ($tmp eq "..") {
                        $tmp = "";
                    }
                }
            } else {
                # Supplied path doesn't exist, so take it as gospel.
                $tmp = $OPTION{"path"};
            }
            if (defined($tmp) && length($tmp) > 1) {
                $module = "$tmp/$pkg";
            } else {
                $module = $pkg;
            }

            if (! $scm->scmobj_propget("repository")) {
                $scm->detect_repository();
            }

            if (-d $pkgfile) {
                chdir($pkgfile);
            }
            $err = $scm->imprt($module);

            # FIXME:  Needed?
            $scm->scmobj_propset("source_tag", "");
            $scm->scmobj_propset("target_tag", "");

            if ($err != MEZZANINE_SUCCESS) {
                eprintf("Import of %s failed.\n", (($pkgfile) ? ($pkgfile) : (".")));
                return $err;
            }
        }
    } else {
        eprint "$pkgfile seems to be neither a file nor a directory.  What should I do?\n";
    }
    return MEZZANINE_SUCCESS;
}

sub
prepare_tree($$)
{
    my ($pkg, $pwd, $pkgdir, $cmd, $err, $msg, $spec, $tmpdir, $params, $tmp);
    my (@srcs, @patches, @tmp);

    # cd into the package directory if one was specified.  If not, use the current directory.
    $pwd = &getcwd();
    if (&pkgvar_filename()) {
        $pkg = &pkgvar_filename();
        $pkgdir = &abs_path($pkg);
    } else {
        $pkgdir = $pwd;
        $pkg = &pkgvar_filename(&basename($pkgdir));
    }
    if (!chdir($pkgdir) && ($pkg ne &basename($pwd))) {
        eprint "Unable to cd to $pkgdir -- $!\n";
        return MEZZANINE_INVALID_PACKAGE;
    }

    # Create the working directory
    &nuke_tree($WORK_DIR);
    if (!&mkdirhier($WORK_DIR)) {
        eprint "Unable to create working directory -- $!\n";
        return MEZZANINE_SYSTEM_ERROR;
    }

    # Copy all the files into their proper places for RPM's use
    if (&pkgvar_type() eq "PDR") {
        $params = "--define '_topdir $pkgdir' --define '_builddir $pkgdir/$WORK_DIR' "
            . "--define '_sourcedir $pkgdir' --define '_specdir $pkgdir'";
        $spec = &find_spec_file(&pkgvar_name(), $pkgdir);
        if (! $spec) {
            eprint "No spec file found in $pkgdir.\n";
            return MEZZANINE_INVALID_PACKAGE;
        }
    } else {
        # Create the RPM build tree
        $tmpdir = &create_temp_space($pkg, "build");
        if (! $tmpdir) {
            return MEZZANINE_SYSTEM_ERROR;
        }
        $spec = &install_spm_files($tmpdir);
        $params = "--define \"_topdir $tmpdir\" --define \"_builddir $pkgdir/$WORK_DIR\"";
    }
    if (! $spec) {
        return MEZZANINE_INVALID_PACKAGE;
    }
    $params .= " --nodeps -bp $spec";
    print "Creating working directory $pkgdir/$WORK_DIR....\n";
    @tmp = &run_cmd("rpmbuild", $params, 0);
    if (($err = shift @tmp) != MEZZANINE_SUCCESS) {
        eprint "Creation of working directory failed.  Error context:\n     ";
        print join("\n     ", ((scalar(@tmp) > 10) ? (splice(@tmp, -10, 10)) : (@tmp))), "\n";
    } elsif ($command eq "prep") {
        print "You may now chdir to ", ($pkgdir eq $pwd ? $WORK_DIR : "$pkg/$WORK_DIR"), " to make changes.\n";
        print "Use \"mzpatch -n <patch_name>\" to generate a patch when done.\n";
    }

    if ($tmpdir) {
        &clean_temp_space($tmpdir);
    }
    chdir($pwd);
    return $err;
}

sub
gen_patch($$)
{
    my ($pkg, $patch, $old_wd, $new_wd, $err, $line_cnt, $pwd, $patchdir);
    my @output;
    local *PATCH;

    $pkg = &pkgvar_filename();
    if ($pkg) {
        $pwd = &getcwd();
        if (!chdir($pkg)) {
            eprint "Unable to chdir to $pkg -- $!\n";
            return MEZZANINE_SYSTEM_ERROR;
        }
    } else {
        $pkg = &basename(&getcwd());
        &pkgvar_filename($pkg);
    }

    $patch = &pkgvar_name();
    if ($patch =~ /^\d+$/) {
        # Redo the spec file to comment out
        # the patch we're about to regenerate.
        if (! &pkgvar_instructions()) {
            my $spec;

            if (&pkgvar_type() eq "PDR") {
                $spec = &find_spec_file(&pkgvar_name(), ".");
            } else {
                $spec = &find_spec_file(&pkgvar_name(), "F");
            }
            if (! $spec) {
                eprint "No spec file found for $pkg.\n";
                return MEZZANINE_INVALID_PACKAGE;
            }
            &pkgvar_instructions($spec);
        }
        dprint "Disabling patch $patch to regenerate.\n";
        if (! &disable_patch($patch)) {
            eprint "Unable to disable patch $patch.\n";
            return MEZZANINE_FILE_OP_FAILED;
        }
    }

    $old_wd = $WORK_DIR;
    $new_wd = $old_wd . "+patched";

    # First, rename the old working directory
    if (-d $new_wd) {
        # Looks like it's already been renamed.
        wprint "Patched working tree $new_wd exists.  I'll assume it's the right one.\n";
        if (-d $old_wd) {
            &nuke_tree($old_wd);
        }
    } elsif (! &move_files($old_wd, $new_wd)) {
        eprint "Unable to move $old_wd to $new_wd -- $!\n";
        return MEZZANINE_SYSTEM_ERROR;
    }
    # Then create a new one using the routine above
    &pkgvar_filename("");
    if (($err = &prepare_tree()) != MEZZANINE_SUCCESS) {
        return $err;
    }

    # Re-enable the patch
    if ($patch =~ /^\d+$/) {
        my $specdata = &parse_spec_file();

        dprint "Re-enabling regenerated patch $patch ($specdata->{PATCH}{$patch}).\n";
        if (! &enable_patch($patch)) {
            eprint "Unable to re-enable patch $patch.\n";
            return MEZZANINE_FILE_OP_FAILED;
        }
        $patch = $specdata->{"PATCH"}{$patch};
    }

    # Now diff the two trees and save the output
    @output = &run_cmd("diff", "-Nur -x '*.orig' -x '*.rej' $old_wd $new_wd", 0);
    if (($err = shift @output) == 2) {
        foreach my $msg (grep(/^diff: /, @output)) {
            eprint "$msg\n";
        }
    }

    # If the patch directory doesn't exist, create it.
    if (!(-d "P") && (-d "F")) {
        my @tmp;

        $patchdir = "P/";
        if (! &mkdirhier("P")) {
            return MEZZANINE_SYSTEM_ERROR;
        }
        if ($local_mode && $scm) {
            print "You requested local mode.  You will need to add the patch directory by hand (mzadd P).\n";
        } elsif ($scm) {
            # FIXME:  This should be ->mkdir()
            $err = $scm->add("P");
            if ($err != MEZZANINE_SUCCESS && $err != MEZZANINE_DUPLICATE) {
                eprint "Addition of directory P failed.\n";
                return $err;
            }
        }
    } elsif (-d "P") {
        $patchdir = "P/";
    } else {
        $patchdir = "";
    }

    # Finally, process the output to save the patch
    if (!open(PATCH, ">$patchdir$patch")) {
        eprint "Unable to open $patchdir$patch for writing -- $!\n";
        return MEZZANINE_SYSTEM_ERROR;
    }
    $line_cnt = 0;
    foreach my $line (@output) {
        next if ($line =~ /^diff: /);
        if ($line =~ /^(diff|---|\+\+\+|\*\*\*)/) {
            $line =~ s/$old_wd\///;
            $line =~ s/\Q$new_wd\E\//mezzanine_patched_/;
        }
        if ($line =~ /^[-I=Rrd\+\s@\*\\]/ || $line =~ /^$/) {
            print PATCH "$line\n";
            $line_cnt++;
        } else {
            dprint "Extra line in diff:  $line";
        }
    }
    close(PATCH);
    print "Created $patchdir$patch ($line_cnt lines).\n";

    # Add the new patch file
    if ($local_mode && $scm) {
        print "You requested local mode.  You will need to add the new patch by hand (mzadd $patchdir$patch).\n";
    } elsif ($scm) {
        $err = $scm->add("$patchdir$patch");
        if ($err != MEZZANINE_SUCCESS && $err != MEZZANINE_DUPLICATE) {
            eprint "Addition of $patchdir$patch failed.\n";
            return $err;
        }
        print "Patch added to SCM.  Use 'mzput' to upload to repository.\n";
    }

    # Cleanup
    &nuke_tree($old_wd);
    &move_files($new_wd, $old_wd);
    chdir($pwd) if ($pwd);

    return MEZZANINE_SUCCESS;
}

sub
merge_file($$)
{
    my ($new_file) = shift;
    my ($pkg, $type, $dest, $pwd, $err);

    if ($new_file =~ /^([SPRF]):(.*)$/) {
        ($type, $new_file) = ($1, $2);
    } elsif ($new_file =~ /^(.*):([SPRF])$/) {
        ($type, $new_file) = ($2, $1);
    } elsif ($new_file =~ /\.(patch|diff)(\.gz|\.bz2|\.Z)?$/) {
        $type = 'P';
    } elsif ($new_file =~ /\.(cpio|tar|cgz|tgz)(\.gz|\.bz2|\.Z)?$/) {
        $type = 'S';
    } elsif ($new_file =~ /\.spec(\.in)?$/) {
        $type = 'F';
    } elsif ($new_file =~ /\.(no)?src\.rpm$/) {
        $type = 'R';
    } elsif ($new_file =~ /^\.mezz/) {
        $type = 'E';
    } else {
        if (-t STDIN) {
            print "Please specify whether $new_file is a (S)ource file, (P)atch, spec (F)ile, or an (E)xtra file ";
            for (; $type !~ /^[SPFE]$/; ) {
                print "[S/P/F/E]:  ";
                chomp($type = <STDIN>);
                $type =~ tr/a-z/A-Z/;
            }
        } else {
            eprint "Unable to guess the type of merge file $new_file.  You must specify\n";
            print "whether it is a (S)ource file, (P)atch, spec (F)ile, or an (E)xtra file\n";
            print "(e.g., \"S:foo-1.0-1.src.rpm\" or \"P:foo-1.0-fix_stuff.patch\").\n";
            return MEZZANINE_BAD_ADDITION;
        }
    }

    $pkg = &pkgvar_filename();
    if ($pkg) {
        $pwd = &getcwd();
        if (!chdir($pkg)) {
            eprint "Unable to chdir into $pkg -- $!\n";
            return MEZZANINE_SYSTEM_ERROR;
        }
        &pkgvar_name(&basename($pkg));
    } else {
        $pkg = &basename(&getcwd());
        &pkgvar_name($pkg);
        &pkgvar_filename(".");
    }

    dprint "Merging $new_file ($type) into $pkg\n";
    if ($type eq 'R') {
        # Merge in new files from an SRPM.
        if ($import_as_pdr) {
            &pkgvar_set("keep_files", undef);
            &convert_srpm_to_pdr($new_file, ".");
        } else {
            # Preserve files as requested.
            if ($config->get("KEEP_FILES") =~ /[FPS]/) {
                my @keep_files;

                @keep_files = split(/[^-\+0-9FPS]+/, $config->get("KEEP_FILES"));
                @keep_files = &find_keepers(@keep_files);
                &pkgvar_set("keep_files", \@keep_files);
            } else {
                &pkgvar_set("keep_files", undef);
            }
            &convert_srpm_to_spm($new_file, ".");
        }
        if ($local_mode) {
            print "$new_file merged successfully.\n";
        } else {
            print "$new_file merged successfully.  Make sure you remove any files and/or\n";
            print "directories which should not be under SCM.  Then run 'mzsync'.\n";
        }
    } else {
        my @output;

        # Copy the file into the appropriate directory
        if ($type eq 'E') {
            $dest = "";
        } else {
            $dest = "$type/";
            if (! -d $type && !&mkdirhier($type)) {
                eprint "Unable to create directory $type -- $!\n";
                return MEZZANINE_SYSTEM_ERROR;
            }
        }
        $dest .= &basename($new_file);
        if (! &cp($new_file, $dest)) {
            eprint "Unable to copy $new_file to $dest -- $!\n";
            return MEZZANINE_SYSTEM_ERROR;
        }

        # Add the file to be committed later.
        if ($local_mode && $scm) {
            print "You requested local mode.  You will need to add the new file(s) by hand (mzadd $dest).\n";
        } elsif ($scm) {
            $err = $scm->add($dest);
            if ($err != MEZZANINE_SUCCESS) {
                eprint "Addition of $new_file ($dest) failed.\n";
            } else {
                print "$new_file merged successfully.\n";
            }
        }
    }
    chdir($pwd) if ($pwd);
    return $err;
}

# Clean up a package tree
sub
clean_package
{
    my ($pkg, $pwd, $err);
    my @output;
    my @nukem;

    $pkg = &pkgvar_filename();
    if ($pkg) {
        $pwd = &getcwd();
        if (!chdir($pkg)) {
            eprint "Unable to chdir to $pkg -- $!\n";
            return MEZZANINE_SYSTEM_ERROR;
        }
    } else {
        $pkg = &basename(&getcwd());
    }

    if ($local_mode) {
        eprint "I'm sorry, but cleaning does not function in local mode.\n";
        return MEZZANINE_SYNTAX_ERROR;
    }

    print "Cleaning and resyncing $pkg, please wait....\n";
    $scm->scmobj_propset("handle_output", 0);
    $err = $scm->get();
    if ($err != MEZZANINE_SUCCESS) {
        print @{$scm->scmobj_propget("saved_output")};
        eprint "Unable to clean $pkg.  (See above error(s).)\n";
        return $err;
    }
    push @output, @{$scm->scmobj_propget("saved_output")};
    $scm->scmobj_propset("handle_output", 1);
    $scm->scmobj_propset("saved_output", "");
    @nukem = grep(/^\?/, @output);
    push @nukem, grep(/skipping directory/, @output);
    find({ "wanted" => sub { ((&basename($_) =~ /^\.?\#/) || (&basename($_) =~ /\~$/)) && push @nukem, $_ },
           "no_chdir" => 1 }, ".");

    if (scalar(@nukem)) {
        foreach my $item (@nukem) {
            chomp($item);
            if ($item =~ /^\?\s+/) {
                $item =~ s/^\?\s+//;
            } elsif ($item =~ /skipping directory/) {
                $item =~ s/^.*skipping directory\s*//;
            }
            print "Removing $item...";
            if ((!&nuke_tree($item)) && (-e $item)) {
                print "unable:  $!.\n";
            } else {
                print ".\n";
            }
        }
    }

    print "Cleanup of $pkg complete.\n";
    chdir($pwd) if ($pwd);
    return MEZZANINE_SUCCESS;
}

# Get a tree back in sync with your current working copy.
sub
sync_package
{
    my ($pkg, $pwd, $err, $done);
    my (@output, @new, @old, @add, @rm);

    $pkg = &pkgvar_filename();
    if ($pkg) {
        $pwd = &getcwd();
        if (!chdir($pkg)) {
            eprint "Unable to chdir to $pkg -- $!\n";
            return MEZZANINE_SYSTEM_ERROR;
        }
    } else {
        $pkg = &basename(&getcwd());
    }

    if ($local_mode) {
        #eprint "You requested local mode.  I will not be able to sync this module.\n";
        #return MEZZANINE_SYNTAX_ERROR;
    }

    print "Syncing $pkg...\n";
    $scm->scmobj_propset("handle_output", 0);
    $scm->scmobj_propset("saved_output", \@output);
    $err = $scm->get();
    if ($err != MEZZANINE_SUCCESS) {
        print @output;
        eprint "Unable to sync repository to working copy of $pkg.  (See above error(s).)\n";
        return $err;
    }
    foreach my $a (grep(/^\?\s/, @output)) {
        chomp($a);
        $a =~ s/^\?\s+//;
        if (($a ne "build.mezz") && ($a ne "work") && ($a ne "work+patched")
            && ($a !~ /\.rpm$/) && ($a !~ /\.deb$/)) {
            push @add, $a;
        }
    }
    push @new, @add;
    foreach my $r (grep(/^U\s/, @output)) {
        chomp($r);
        $r =~ s/^U\s+//;
        push @rm, $r;
    }
    push @old, @rm;
    if (! $local_mode) {
        for (@output = (); scalar(@add); ) {
            dprint "Adding...  ", join(" ", @add), "\n";
            if ($scm->add(@add) != MEZZANINE_SUCCESS) {
                print @output;
                wprintf("Add for %s failed.  (See above error(s).)\n",
                        join(" ", @add));
                next;
            }
            @add = ();
            foreach my $a (grep(/^\?\s/, @output)) {
                chomp($a);
                $a =~ s/^\?\s+//;
                push @add, $a;
            }
            push @new, @add;
        }
    }
    if (scalar(@rm)) {
        dprint "Removing...  ", join(" ", @rm), "\n";
        if ($local_mode) {
            foreach my $path (@rm) {
                &nuke_tree($path);
            }
        } else {
            if ($scm->remove(@rm) != MEZZANINE_SUCCESS) {
                print @output;
                wprintf("Remove for %s failed.  (See above error(s).)\n",
                        join(" ", @rm));
                @old = ();
            }
        }
    }

    if (!scalar(@new) && !scalar(@old)) {
        print "No additions or removals were needed.\n";
    } else {
        if (scalar(@new)) {
            print "Added files/directories:  ", join(" ", sort(@new)), "\n";
        }
        if (scalar(@old)) {
            print "Removed files/directories:  ", join(" ", sort(@old)), "\n";
        }
    }
    print "Sync of $pkg complete.  Your changes will not be finalized until you commit them.\n";
    chdir($pwd) if ($pwd);
    return MEZZANINE_SUCCESS;
}

# Move repository files around
sub
move_package_files
{
    my @flist = @_;
    my ($target, $err, $done);
    my (@output, @add, @rm);

    $target = pop(@flist);
    $scm->scmobj_propset("handle_output", 0);
    $scm->scmobj_propset("saved_output", \@output);

    $err = $scm->move(@flist, $target);
    if ($err != MEZZANINE_SUCCESS) {
        print @output;
        eprint "Unable to move files.  (See above error(s).)\n";
        return $err;
    }

    return MEZZANINE_SUCCESS;
}

# main() here is basically the same as main() in C
sub
main
{
    my $err;
    my @flist;

    &mezz_init("srctool", "3.0", "help|h", "version|v", "debug|d!",
               "import|i", "prepare|p", "merge|m", "resync|r",
               "patch|apply|a", "move|mv", "name|n=s", "clean|c",
               "repository|dir|D=s", "local|L", "flat|pdr|f",
               "path|relpath|R=s", "protocol|P=s", "keep|k=s",
               "savecfg!");

    if ($OPTION{"version"}) {
        &print_version($PROGNAME, $VERSION, "Michael Jennings <mej\@eterm.org>",
                       'CVS Revision $Revision: 1.62 $ created on $Date: 2006/09/26 20:13:30 $ by $Author: mej $ ');
    }
    if ($OPTION{"help"}) {
        &print_usage_info();
    }
    select STDOUT; $| = 1;
    $config = Mezzanine::Config->new("srctool/config");
    if (!scalar($config->keys()) && ((!defined($OPTION{"savecfg"})) || ($OPTION{"savecfg"}))) {
        $OPTION{"savecfg"} = 1;
    }

    if (defined($OPTION{"debug"}) && !($OPTION{"debug"})) {
        &debug_set($config->set("DEBUG", 0));
    } else {
        &debug_set($config->set("DEBUG", $OPTION{"debug"} || $config->get("DEBUG") || 0));
    }
    &pkgvar_name($OPTION{"name"});
    if (scalar(@ARGV)) {
        @flist = @ARGV;
    }
    $config->set("DEFAULT_SCM", $OPTION{"protocol"} || $config->get("DEFAULT_SCM") || "cvs");

    if ($OPTION{"protocol"}) {
        $scm = Mezzanine::SCM->new($OPTION{"protocol"});
    } elsif (scalar(@ARGV)) {
        $scm = Mezzanine::SCM->auto_detect($ARGV[0]);
    } else {
        $scm = Mezzanine::SCM->auto_detect('.');
    }
    if (! $scm) {
        if (! ($scm = Mezzanine::SCM->new($config->get("DEFAULT_SCM")))) {
            wprint "Unable to initialize SCM.  Using local mode.\n";
            $local_mode = 1;
        }
    }

    if ($OPTION{"local"}) {
        if ($scm) {
            $scm->scmobj_propset("command", "/bin/true");
        }
        $local_mode = 1;
    }

    $local_mode = $config->set("LOCAL_MODE", $local_mode || 0);
    $config->set("REPOSITORY", $OPTION{"repository"} || $config->get("REPOSITORY"));
    $config->set("KEEP_FILES", uc($OPTION{"keep"}) || uc($config->get("KEEP_FILES")));

    # Save configuration if needed.
    if ($OPTION{"savecfg"}) {
        $config->save();
    }

    if ($scm) {
        $scm->scmobj_propset("repository", $config->get("REPOSITORY"));
    }

    if ($0 =~ /mzimport$/ || $OPTION{"import"}) {
        $command = "import";
        if ($OPTION{"flat"}) {
            $import_as_pdr = 1;
        }
        @flist = &download_files(@flist);
        if (scalar(@flist)) {
            foreach my $f (@flist) {
                my $err;

                &pkgvar_filename($f);
                $err = &import_package();
                return $err if ($err);
            }
        } else {
            return &import_package();
        }
    } elsif ($0 =~ /mzprep$/ || $OPTION{"prepare"}) {
        $command = "prep";
        if (scalar(@flist)) {
            foreach my $f (@flist) {
                my $err;

                &pkgvar_filename($f);
                $err = &prepare_tree();
                return $err if ($err);
            }
        } else {
            return &prepare_tree();
        }
    } elsif ($0 =~ /mzmerge$/ || $OPTION{"merge"}) {
        my $pkg;

        $command = "merge";
        if ($OPTION{"flat"}) {
            $import_as_pdr = 1;
        }
        @flist = &download_files(@flist);
        foreach my $f (@flist) {
            if (-d $f) {
                $pkg = $f;
                last;
            }
        }
        if ($pkg) {
            @flist = grep($_ ne $pkg, @flist);
        } elsif (! -d "F") {
            eprint "No valid module specified.\n";
            return MEZZANINE_BAD_MODULE;
        } else {
            $pkg = "";
        }
        foreach my $f (@flist) {
            my $err;

            &pkgvar_filename($pkg);
            $err = &merge_file($f);
            return $err if ($err);
        }
    } elsif ($0 =~ /mzpatch$/ || $OPTION{"patch"}) {
        my ($pkg, $patch);

        $command = "patch";
        if (! $pkg) {
            $pkg = ".";
            &pkgvar_filename($pkg);
            if (! &pkgvar_type()) {
                eprint "No package was specified, and the current directory doesn't look like one.\n";
                return MEZZANINE_SYNTAX_ERROR;
            }
        }
        if (!($patch = &pkgvar_name())) {
            if (scalar(@flist) == 2) {
                if (-d $flist[0]) {
                    ($pkg, $patch) = @flist;
                } elsif (-d $flist[1]) {
                    ($patch, $pkg) = @flist;
                }
            } elsif (scalar(@flist) == 1) {
                if (-d $flist[0]) {
                    $pkg = $flist[0];
                } else {
                    $patch = $flist[0];
                }
            } else {
                eprint "No patch name was specified.\n";
                return MEZZANINE_SYNTAX_ERROR;
            }
        }
        &pkgvar_filename($pkg);
        &pkgvar_name($patch);
        return &gen_patch();
    } elsif ($0 =~ /mzclean$/ || $OPTION{"clean"}) {
        $command = "clean";
        if (scalar(@flist)) {
            foreach my $f (@flist) {
                my $err;

                &pkgvar_filename($f);
                $err = &clean_package();
                return $err if ($err);
            }
        } else {
            return &clean_package();
        }
    } elsif ($0 =~ /mz(re)?sync$/ || $OPTION{"resync"}) {
        $command = "sync";
        if (scalar(@flist)) {
            foreach my $f (@flist) {
                my $err;

                &pkgvar_filename($f);
                $err = &sync_package();
                return $err if ($err);
            }
        } else {
            return &sync_package();
        }
    } elsif ($0 =~ /mzmv$/ || $OPTION{"move"}) {
        $command = "move";
        if (scalar(@flist) > 1) {
            $err = &move_package_files(@flist);
            return $err if ($err);
        } else {
            eprint "Two or more parameters required for move.\n";
            return MEZZANINE_SYNTAX_ERROR;
        }
    } else {
    }
    return MEZZANINE_SUCCESS;
}

exit &main();
