#!/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;

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
  -s            Save the detected layout configuration (has effect only if a
                checkout is done after the inject)

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_savecfg;
my $opt_dbgsdcommon;
#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,
   "s"                     => \$opt_savecfg,
   "dbgsdcommon"           => \$opt_dbgsdcommon
);

if (!defined $opt_dbgsdcommon) {
   use lib "/usr/share/svn-buildpackage";
} else {
   use lib ".";
};
use SDCommon;
$ENV{"SVN_BUILDPACKAGE"} = $SDCommon::version;

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

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

$SDCommon::opt_nosave = 1 ;
$SDCommon::opt_nosave = 0 if (defined $opt_savecfg) ;

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

sub tmpdirexec {
   my $testfile=`mktemp`;
   chomp $testfile;
   open (O, ">$testfile");
   print O '#!/bin/sh\necho ok\n';
   close O;
   print "Checking if the default \$TMPDIR allows execution...\n" ;
   if (system ("chmod", "+x", "$testfile") and (`$testfile`) =~ /ok/ ) {
      print "Default \$TMPDIR allows execution.\n"
   } else {
      print "Default \$TMPDIR does NOT allow execution.\n";
      print "All temporary files will be created in the current directory.\n";
      $ENV{"TMPDIR"}=getcwd();
   };
   system ( "rm -f $testfile" );
}

&tmpdirexec;

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(".");
   }

   &printImportDetails;

   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;
}

if (defined($opt_nobranches)) {
   $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;


sub printImportDetails ()
{
    return 0 if (!defined $opt_debug);
    my $cwd=getcwd();
    # XXX: debug stuff, remove or disable!
    print ("Import details:\n \$package: $package\n \$opt_svnurl=$opt_svnurl\n \$opt_layout=$opt_layout\n");
    print (" cwd=" . $cwd. "\n" );
    if ( $cwd =~ /^\/tmp/ ) { system ( "tree $cwd" ) ; };
    print ("Press ^C to stop or Enter to continue!");
    my $dummy=<STDIN>;
    return 0;
}

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;

my ($subupsCurrent, $subtrunk, $subupsVersion, $subtags, $subupsTags);

if ($opt_layout == 1) {
    $subupsCurrent="$package/branches/upstream/current";
    $subupsVersion="$package/branches/upstream/$upsVersion";
    $subupsTags="$package/branches/upstream";
    $subtrunk="$package/trunk";
    $subtags="$package/tags";
} else {
    $subupsCurrent="branches/upstream/$package/current";
    $subupsVersion="branches/upstream/$package/$upsVersion";
    $subupsTags="branches/upstream/$package";
    $subtrunk="trunk/$package";
    $subtags="tags/$package";
}

$SDCommon::c{"trunkUrl"} = "$opt_svnurl/$subtrunk";
$SDCommon::c{"tagsUrl"} = "$opt_svnurl/$subtags";
$SDCommon::c{"upsCurrentUrl"}="$opt_svnurl/$subupsCurrent";
$SDCommon::c{"upsTagUrl"}="$opt_svnurl/$subupsTags";

# preparing a package tree that will be inserted into repository later
if($dscOrig) {
   # prepare the upstream source
   withecho "mkdir", "-p", "$subupsTags";
   chdir "$subupsTags";

   # 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
   my $dir = dirname ("$subtrunk") ;
   my $base = basename("$subtrunk")  ;
   withecho "mkdir", "-p", $dir;
   chdir $dir;
   withecho "dpkg-source -x $opt_dsc";
   system "rm -f *.gz *.bz2";
   withecho "mv * $base";
}

chdir $tempdir;

# Final tree prepation before commit, preconfiguring already

if($changed_non_debian || !$opt_onlychanged) {


    &printImportDetails;

    # for non-native packages the source is in uptream/current
    if ($dscOrig) {
        withecho ("svn", $opt_svnquiet, "import", "-m", "$scriptname Installing original source of $package",
            "$subupsTags", $SDCommon::c{"upsTagUrl"} );
    }
    else {
        withecho ("svn", $opt_svnquiet, "import", "-m", "$scriptname Installing original source of $package",
            "$subtrunk", "$opt_svnurl/$subtrunk" );
    } ;

}

# make sure all directories up to that level are created
SDCommon::svnMkdirP ( $SDCommon::c{"tagsUrl"} ) ;

# 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/$subupsCurrent",
           "$opt_svnurl/$subupsVersion", $opt_svnquiet);

       # the destination directory must not exist, but all up to the last level must be there
       SDCommon::svnMkdirP ( dirname("$opt_svnurl/$subtrunk") ) ;
       withecho("svn", "-m", "$scriptname Forking $package source to Trunk", "copy",
           "$opt_svnurl/$subupsCurrent",
           "$opt_svnurl/$subtrunk", $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_svnurl/$subtrunk", "$tempdir/trunk", "$tempdir/unpdir/$dirname");

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

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

chdir $basedir;

my $trunkdir;

if($use_this_repo) {
   # FIXME: this doesn't take layout into account, does it?
   $trunkdir = "$package/trunk";
   withecho "svn up";
}
else {
   if($opt_checkout==2) {
      # checkout everything
      if ($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_layout==2) {
         print "Full checkout with layout 2 is not supported. Falling back to trunk checkout.";
         $opt_checkout=1;
      }
   };

   if ($opt_checkout==1) {
      my $svnloc = $SDCommon::c{"trunkUrl"};
      $trunkdir = "$basedir/$package";
      print "Storing trunk copy in $basedir/$package.\n";
      #withecho("cp", "-a", "$tempdir/trunk",  $trunkdir);
      withecho "svn", "checkout", "$svnloc", "$basedir/$package", $opt_svnquiet ;
   };
}

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);
}

