#!/usr/bin/perl -w
# Copyright Eduard Bloch <blade@debian.org>, 2003, 2004, 2005, 2006
# Changes by Steve Kowalik <stevenk@debian.org>, 2005
# License: GPL

use Getopt::Long qw(:config no_ignore_case bundling);
use File::Basename;
use Cwd;
#use diagnostics;
use strict;

use lib "/usr/share/svn-buildpackage";
use SDCommon;
$ENV{"SVN_BUILDPACKAGE"} = $SDCommon::version;

my $basedir=getcwd;
my $scriptname="[svn-inject]";

sub help {
        print <<END
Usage: svn-inject [options] <package>.dsc [ <repository URL> ]
Options:
  -h            print this message
  -v            Make the commands verbose
  -q            Don't show command calls
  -l <digit>    Layout type (1=pkg/function, 2=function/pkg/)
  -t <string>   Directory where you like to store the .orig files
  --add-tar     Keep tarballs in the repository
  -o            Only keep modified files under SVN control (incl. debian/ dir),
                track only parts of upstream branch
  -c <digit>    Checkout the tree after injecting
                (0=don't do, 1=trunk only (default), 2=complete tree)
  -d <string>   Do-Like-OtherPackage feature. Looks at a local working
                directory, removes lastword/trunk from its URL and uses
                the result as base URL
  --no-branches Like -o but never tracking upstream branch

If the base repository URL is omited, svn-inject tries to get it from
the current directory. In this case, -c becomes ineffective.

END
;
exit 1;
}

#  -T name      2nd level upstream (3rd party packages) tracking mode, whole
#               package is imported as pure upstream source
#

my $initial_run;
my $opt_debug;
my $opt_svnurl;
my $opt_layout=1;
my $opt_quiet;
my $opt_tardir;
my $opt_checkout=1;
my $opt_dolike;
my $opt_addtar;
my $opt_help;
my $opt_verbose;
my $opt_onlychanged;
my $opt_nobranches;
#my $opt_trackmode;

# parse Command line
my %options = (
   "h|help"                => \$opt_help,
   "v|verbose"             => \$opt_verbose,
   "q|quiet"             => \$opt_quiet,
   "t=s"                     => \$opt_tardir,
   "d=s"                     => \$opt_dolike,
   "l=i"                     => \$opt_layout,
   "o"                     => \$opt_onlychanged,
#   "u=s"                     => \$opt_trackmode,
   "add-tar"                     => \$opt_addtar,
   "c=i"                   => \$opt_checkout,
   "O|no-branches"           => \$opt_nobranches
);

&help unless ( GetOptions(%options));
&help if ($opt_help);
&help if $#ARGV < 0;

$ENV{"SVN_BUILDPACKAGE"} = $SDCommon::version;
$SDCommon::opt_verbose=$opt_verbose;

my $opt_dsc=$ARGV[0];
my $use_this_repo;

die "-c 2 only works with -t 1\n" if($opt_checkout==2 && $opt_layout!=1);

if($opt_dolike) {
    if (!$opt_svnurl) {
        $opt_svnurl=url($opt_dolike);
        ($opt_svnurl=~s/\/[^\/]+\/trunk$//) or die "Failed to extract the base URL, maybe not in layout type 2?\n";
    }
    $basedir=long_path(dirname($opt_dolike)) if ! $basedir;
    if(!$opt_tardir && open(my $dl,"$opt_dolike/.svn/deb-layout")) {
        while(<$dl>) {
            if(/^origDir\s*=\s*(.*)/) {
                $opt_tardir=$1;
                last;
            }
        }
    }
    print "Got base URL: $opt_svnurl\n";
    print "Working directory goes to $basedir/\n";
    print "Tarball to $opt_tardir/ or so...\n";
    $opt_onlychanged = length(`svn proplist $opt_dolike/debian | grep mergeWithUpstream`) if !$opt_onlychanged;
}
else {
   $opt_svnurl=$ARGV[1];
   $opt_svnurl=~s,/$,,;
   if(! defined($opt_svnurl)) {
      # we use the current directory and its reflection in the repository as
      # base for the package tree
      $use_this_repo=1;
      $opt_svnurl=url(".");
   }

   if(! ($opt_dsc && $opt_svnurl)) {
      die "Need two arguments: <dsc file> <SVN url>\n";
   }
}

die "Dsc file $opt_dsc not readable!\n" if (! -r $opt_dsc);

chomp(my $tempdir=`mktemp -d`);

$opt_dsc = long_path($opt_dsc);

my $opt_svnquiet="-q";
my $opt_patchquiet="--silent";
my $opt_tarquiet;

if($opt_verbose) {
   undef $opt_svnquiet;
   $opt_tarquiet="-v";
   undef $opt_patchquiet;
}

$opt_onlychanged+=$opt_nobranches;

#$SDCommon::opt_quiet=$opt_quiet;


open(my $dsc, "<$opt_dsc") || die "Could not read $opt_dsc";
my $fromDir=dirname($opt_dsc);

my $dscOrig;
my $upsVersion;

my $package;
my $debVersion;
my $dscOrigFilename;
my $dscDiff;

while(<$dsc>) {
   # NEVER USE abs_path HERE, it resolves the links
   $package=$1 if(/^Source: (.+)\n/);
   $debVersion=$1 if(/^Version: (.+)\n/ && !$debVersion);
   if(/^(\s\w+\s\d+\s+)((.*)_(.*).orig.tar.(gz|bz2))/)
   {
      $dscOrig="$fromDir/$2";
      $dscOrigFilename=$2;
      $upsVersion=$4;
   }
   $dscDiff = "$fromDir/$1" if(/^\s\w+\s\d+\s(.+\.diff.(gz|bz2))\n/);
}
close($dsc);

if($opt_checkout && -d "$basedir/$package") {
   die "$basedir/$package already exists, aborting...\n";
}

$dscDiff=long_path($dscDiff);

if($dscOrig) {
   $opt_tardir=long_path($opt_tardir ? $opt_tardir : "$basedir/tarballs");
   mkdir $opt_tardir if(!-d $opt_tardir);
   withechoNoPrompt("cp", "-l", $dscOrig, $opt_tardir) || withecho("cp", $dscOrig, $opt_tardir);
}

$SDCommon::c{"origDir"}=long_path($opt_tardir);

my %ourfiles;
my $changed_non_debian=0;
# creating the list of relevant files for mergeWithUpstream mode
if($opt_onlychanged && $dscDiff) {
   open(my $dl, (($dscDiff=~/bz2/i)?"bz":"z")."cat $dscDiff|");
   while(<$dl>) {
       if(/^\+\+\+\ [^\/]+\/(.+)\n/) {
           my $file=$1;
           $ourfiles{$file}=1;
           $changed_non_debian += ( ! ($file=~/^debian/));
       }
   }
   close($dl);
}
$changed_non_debian = 0 if($opt_nobranches); # ignore their original versions in upstream branch

chdir $tempdir;

# preparing a package tree that will be inserted into repository later
if($dscOrig) {
   # prepare the upstream source
   my $ups_branch_dir=($opt_layout==1 ? "$package/branches/upstream" : "branches/upstream/$package");
   withecho "mkdir", "-p", $ups_branch_dir;
   chdir $ups_branch_dir;

   # extract the whole package and use its Debian version as upstream version
   withecho "tar", $opt_tarquiet, ($dscOrig=~/bz2$/i ? "-j" : "-z"), "-x", "-f", $dscOrig;
   oldSvnDirsCheck ".";

   my @filesInside=(<*>);

   if($#filesInside > 0) {
       # my favorite cruft, orig tarballs without the single top level
       # directory. Create an extra directory for them rather then renaming
       # the dir into "current"
       mkdir "current";
   }
   withecho("mv",@filesInside, "current");
   
   if($opt_onlychanged) {
       chdir "current" || die "Internal operation error, unable to create local import directory\n"; # code 42, stop before unlinking anything
       del_unreferenced(".", %ourfiles);
   }

}
else {
   # native packages are easier
   mkdir $package;
   chdir $package;
   withecho "dpkg-source -x $opt_dsc";
   system "rm -f *.gz *.bz2";
   withecho "mv * trunk";
}

chdir $tempdir;

# Final tree prepation before commit, preconfiguring already
mkdir "$package/tags";
$SDCommon::c{"tagsUrl"}="$opt_svnurl/$package/tags";

if($changed_non_debian || !$opt_onlychanged) {

    if ($opt_layout == 1) {
        mkdir "$package/tags";
        $SDCommon::c{"tagsUrl"}="$opt_svnurl/$package/tags";
        $SDCommon::c{"upsCurrentUrl"}="$opt_svnurl/$package/branches/upstream/current";
        $SDCommon::c{"upsTagUrl"}="$opt_svnurl/$package/branches/upstream";
    } elsif ($opt_layout == 2) {
        mkdir "tags/$package";
        $SDCommon::c{"tagsUrl"}="$opt_svnurl/tags/$package";
        $SDCommon::c{"upsCurrentUrl"}="$opt_svnurl/branches/upstream/$package/current";
        $SDCommon::c{"upsTagUrl"}="$opt_svnurl/branches/upstream/$package";
    }

#withecho "svn $opt_svnquiet import -m\"$scriptname Installing original source of $package\" $package $opt_svnurl/$package";

    withecho ("svn", $opt_svnquiet, "import", "-m", "$scriptname Installing original source of $package", 
        ($opt_layout==2) ? (".", $opt_svnurl) : ($package, "$opt_svnurl/$package"));

}

# for non-native: create the trunk copy from the source and modify it
if($dscOrig) {
    if($changed_non_debian>0 || !$opt_onlychanged) {
#        withecho("svn", "-m", "$scriptname Tagging upstream source version of $package", "copy",
#            "$opt_svnurl/$package/branches/upstream/current",
#            "$opt_svnurl/$package/branches/upstream/$upsVersion", $opt_svnquiet);
#        withecho("svn", "-m", "$scriptname Forking $package source to Trunk", "copy",
#            "$opt_svnurl/$package/branches/upstream/current",
#            "$opt_svnurl/$package/trunk", $opt_svnquiet);
        if ($opt_layout == 1) {
            withecho("svn", "-m", "$scriptname Tagging upstream source version of $package", "copy",
                "$opt_svnurl/$package/branches/upstream/current",
                "$opt_svnurl/$package/branches/upstream/$upsVersion", $opt_svnquiet);
            withecho("svn", "-m", "$scriptname Forking $package source to Trunk", "copy",
                "$opt_svnurl/$package/branches/upstream/current",
                "$opt_svnurl/$package/trunk", $opt_svnquiet);
        } else {
            foreach (('trunk', 'tags')) { # FIXME, make it nicer
                if (system("svn", "ls", "$opt_svnurl/$_")) {
                    withecho("svn", "-m", "$scriptname Creating $_", "mkdir", "$opt_svnurl/$_");
                }
            }
            withecho("svn", "-m", "$scriptname Creating tags/$package", "mkdir", "$opt_svnurl/tags/$package");
            withecho("svn", "-m", "$scriptname Tagging upstream source version of $package", "copy",
                "$opt_svnurl/branches/upstream/$package/current",
                "$opt_svnurl/branches/upstream/$package/$upsVersion", $opt_svnquiet);
            withecho("svn", "-m", "$scriptname Forking $package source to Trunk", "copy",
                "$opt_svnurl/branches/upstream/$package/current",
                "$opt_svnurl/trunk/$package", $opt_svnquiet);
        }
    }
   mkdir "unpdir";
   chdir "unpdir";
   withecho "dpkg-source -x $opt_dsc";
   system "rm -f *.gz *.bz2";
   # now use svn_load_dirs to upgrade the trunk fork to Debian versions.
   # For mergeWithUpstream mode, drop all unchanged files
   my $dirname=<*>;
   chdir $dirname;
   withecho "fakeroot debian/rules clean || debian/rules clean";
   del_unreferenced(".", %ourfiles) if $opt_onlychanged;
   load_dirs(
       ($opt_layout==1 ? "$opt_svnurl/$package/trunk" : "$opt_svnurl/trunk/$package") , 
       "$tempdir/trunk", "$tempdir/unpdir/$dirname");

   if ($opt_onlychanged) {
       withecho "svn", "propset", "mergeWithUpstream", 1, "$tempdir/trunk/debian";
   }

   withecho("svn", "commit", "-m", "$scriptname Applying Debian modifications to trunk", "$tempdir/trunk");
}

chdir $basedir;

my $trunkdir;

if($use_this_repo) {
   $trunkdir = "$package/trunk";
   withecho "svn up";
}
else {
   if($opt_checkout==2 && $opt_layout==1) {
      $trunkdir = "$basedir/$package/trunk";
      print "Storing copy of your repository tree in $basedir/$package.\n";
      withecho "svn", "checkout", "$opt_svnurl/$package", "$basedir/$package", $opt_svnquiet;
   }
   elsif ($opt_checkout==1) {
      $trunkdir = "$basedir/$package";
      print "Storing trunk copy in $basedir/$package.\n";
      withecho("cp", "-a", "$tempdir/trunk",  $trunkdir);
   }
}

chdir $trunkdir if($trunkdir);

SDCommon::writeCfg ".svn/deb-layout" if($opt_checkout>0);
print "Done!\n";
print ("Your working directory is $trunkdir - have fun!\n") if($trunkdir);

sub END {
    if ($tempdir && -e $tempdir) {
        print "Removing tempdir $tempdir.\n";
        system "rm -rf $tempdir" ;
    }
}

# broken, keeps subdirs
#sub del_unreferenced {
#    (my $dir, my %list) = @_;
#
#    #for(keys %list) {print "goodstuff: $_\n"};
#    for my $file (reverse(sort(`cd $dir ; find`))) { # make sure directories come last on each level
#        chomp($file);
#        substr($file,0,2,"");
#        next if(!length($file));
#        print "del? $file\n";
#        if(!exists $list{$file}) {
#            unlink("$dir/$file");
#            rmdir(basename("$dir/$file")); # will succeed if empty
#        }
#    }
#}

sub del_unreferenced {
    my $dir=Cwd::abs_path(shift);
    my %list = shift;
    chomp(my $tmpdir = `mktemp -d`);
    # withecho("cp", "-a", "--parents", (keys %ourfiles), "../current2"); sucks, cannot ignore errors "properly"
    # this sucks too
    withecho("cd $dir ; tar $opt_tarquiet -c ".join(' ',keys %ourfiles)." 2>/dev/null | tar x $opt_tarquiet -C $tmpdir");
    withecho("rm", "-rf", $dir);
    withecho("mv", $tmpdir, $dir);
}

