#!/usr/bin/perl -w # $Id: dasscm 209 2007-07-02 16:01:28Z joergs $ use strict; use Env qw($DASSCM_PROD $DASSCM_REPO $USER $DASSCM_USERNAME $DASSCM_USER $DASSCM_PASSWORD); use Cwd; use POSIX qw/getpgrp tcgetpgrp/; use Term::ReadKey; use File::Basename; use File::Compare; use File::Copy; use File::Find; use File::stat; use File::Path; use Getopt::Long; # # used ConfigFile instead of SmartClient::Config, # because the huge amount of SmartClient dependencies #use SmartClient::Config; use ConfigFile; ##################################################################### # # global # my $config_file = "/etc/dasscm.conf"; # my $config = SmartClient::Config->( $config_file ); my $config = ConfigFile::read_config_file($config_file); my $DASSCM_LOCAL_REPOSITORY_BASE; my $DASSCM_REPOSITORY_NAME; my $DASSCM_SVN_REPOSITORY; my $SVN = "svn "; my $svnOptions = ""; my $svnCheckoutCredentials = ""; my $svnPasswordCredentials = ""; # command line options get stored in options hash my %options = (); # subcommand, that gets executed (add, commit, ...) my $command; my $verbose = 0; # stores result from status (cvscheck) my %status_removedfiles = (); my %status_changedfiles = (); ##################################################################### # # util functions # sub usage() { print "usage: dasscm [options] [args]\n"; print "\n"; print "dasscm is intended to help versioning configuration files\n"; print "\n"; print "Available subcommands:\n"; print " init\n"; print " login\n"; print " add \n"; print " commit \n"; print " diff \n"; print " help \n"; print "\n"; print "preperation:\n"; print "check out the configuration repository, e.g.\n"; print "svn checkout --no-auth-cache --username USERNAME https://dass-it.de/svn/dasscm/HOSTNAME\n"; print "environment variables\n", " DASSCM_REPO\n", " DASSCM_PROD¸n", " DASSCM_USERNAME\n", " DASSCM_PASSWORD\n", "are evaluated.\n"; print "\n"; } sub check_env() { # DASSCM_PROD if ( !$DASSCM_PROD ) { $DASSCM_PROD = "/"; } if ( !-d $DASSCM_PROD ) { die "DASSCM_PROD ($DASSCM_PROD) is not set to a directory.\n"; } if ($verbose) { print "DASSCM_PROD: " . $DASSCM_PROD . "\n"; } # DASSCM_REPOSITORY_NAME if ( !$DASSCM_REPOSITORY_NAME ) { die "Variable DASSCM_REPOSITORY_NAME is not defined.\nIt needs to be a unique name.\nNormally the full qualified host name is used.\nUse file $config_file to configure it.\n"; } # DASSCM_REPO if ( !$DASSCM_REPO ) { if ( $DASSCM_LOCAL_REPOSITORY_BASE && $DASSCM_REPOSITORY_NAME ) { $DASSCM_REPO = $DASSCM_LOCAL_REPOSITORY_BASE . "/" . $DASSCM_REPOSITORY_NAME; } else { die "Envirnonment variable DASSCM_REPO not set.\nSet DASSCM_REPO to the directory of the versioning system checkout for this machine.\n"; } } print "DASSCM_REPO: " . $DASSCM_REPO . "\n"; # # check if local repository directory exist (if not creating by init) # if ( $command ne "init" ) { if ( not -d $DASSCM_REPO ) { die "Can't access local repository DASSCM_REPO\n($DASSCM_REPO)\nCheck configuration and execute\n dasscm init\n"; } # # user settings # # DASSCM_USER is legacy. Use DASSCM_USERNAME instead if ( !$DASSCM_USERNAME ) { $DASSCM_USERNAME = $DASSCM_USER; } # user root is not allowed for checkins. # if user is root, DASSCM_USER has to be set, # otherwise USER can be used if ( "$USER" eq "root" ) { if ( ( not $DASSCM_USERNAME ) and ( $command ne "login" ) ) { die "Envirnonment variable DASSCM_USERNAME not set.\nSet DASSCM_USERNAME to your subversion user account.\n"; } $svnOptions .= " --no-auth-cache "; } elsif ( !$DASSCM_USERNAME ) { $DASSCM_USERNAME = $USER; } # # password # if ($DASSCM_PASSWORD) { $svnPasswordCredentials = " --password $DASSCM_PASSWORD "; } } #$svnOptions .= " --username $DASSCM_USERNAME " } sub check_parameter(@) { } sub get_filenames(@) { my $filename_prod = $_[0]; if ( !( $filename_prod =~ m/^\// ) ) { $filename_prod = cwd() . "/" . $filename_prod; } -r $filename_prod or die "$filename_prod is not accessable"; # TODO: dirname buggy: eg. "/etc/" is reduced to "/", # "/etc" is used as filename my $dirname_prod = dirname($filename_prod); chdir $dirname_prod or die $!; $dirname_prod = cwd(); my $basename = basename($filename_prod); print "dir: " . $dirname_prod . "\n"; print "fn: " . $basename . "\n"; my $dirname_repo = $DASSCM_REPO . "/" . $dirname_prod; my $filename_repo = "$dirname_repo/$basename"; return ( $basename, $dirname_prod, $dirname_repo, $filename_prod, $filename_repo ); } # # used by status # checks for differences between PROD and (local) REPO # sub cvscheck { return unless -f; # keine Directories return if $File::Find::dir =~ /\/CVS$/; # ignoriere CVS-Verzeichnisse return if $File::Find::dir =~ /\/\.svn/; # ignoriere Subversion Verzeichnisse (inkl. Unterverzeichnisse) my $cvsworkfile = "$File::Find::dir/$_"; # Ursprungspfad ermitteln # TODO: get_filename ? $cvsworkfile =~ /${DASSCM_REPO}\/(.+)/; my $realfile = "/" . $1; # relativer Pfad zur CVS-Arbeitsdatei my $relcvsworkfile = $1; if ( !-r $realfile ) { $status_removedfiles{"$realfile"} = $cvsworkfile; } else { ( -r "$cvsworkfile" ) || die("Fehler: $cvsworkfile ist nicht lesbar"); if ( compare( $cvsworkfile, $realfile ) != 0 ) { # Dateien unterscheiden sich #(-w $cvsworkfile) || die("failed: no Fehler: kein Schreibrecht auf $cvsworkfile"); # Arbeitskopie durch Kopie ersetzen #copy($realfile,$cvsworkfile) || die("Fehler beim kopieren $realfile --> $cvsworkfile"); $status_changedfiles{"$realfile"} = $cvsworkfile; } } } sub run_command { my $command = shift; #print "executing command: " . $command . "\n"; open( RESULT, $command . ' 2>&1 |' ); my @result = ; close(RESULT); my $retcode = $? >> 8; #print @result; #if( $retcode ) { print "return code: " . $retcode . "\n"; } return ( $retcode, @result ); } sub run_interactive { if ($verbose) { print "run_interactive:" . join( " ", @_ ) . "\n"; } system(@_); if ( $? == -1 ) { printf "failed to execute: $!\n"; } elsif ( $? & 127 ) { printf "child died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without'; } elsif ( $? >> 8 != 0 ) { printf "child exited with value %d\n", $? >> 8; } return ( $? >> 8 ); } sub svn_check_credentials( $$ ) { my $username = shift; my $password = shift; ( my $rc_update, my @result ) = run_command( "$SVN info --non-interactive --no-auth-cache --username $username --password $password $DASSCM_SVN_REPOSITORY" ); print @result; if ( $rc_update != 0 ) { print @result; die; } } sub svn_update( ;$ ) { my $update_path = shift || $DASSCM_REPO; ( my $rc_update, my @result ) = run_command("$SVN update $svnCheckoutCredentials $update_path"); if ( $rc_update != 0 ) { print @result; die; } print @result; } ##################################################################### # # functions sub help(;@) { if ( @_ == 0 ) { usage(); } else { print "help for @_: ...\n"; } } sub login(@) { check_parameter( @_, 1 ); check_env(); my $output_username = ""; if ($DASSCM_USERNAME) { $output_username = " ($DASSCM_USERNAME)"; } print "Enter DASSCM user name", $output_username, ": "; my $input_username = ; chomp($input_username); # hidden password input print "Enter DASSCM user password: "; ReadMode('noecho'); my $input_password = ; ReadMode('normal'); chomp($input_password); svn_check_credentials( $input_username, $input_password ); # # set environment variables # $ENV{'DASSCM_USERNAME'} = $input_username; $ENV{'DASSCM_PASSWORD'} = $input_password; print "subversion access okay\n\n", "DASSCM_USERNAME: $input_username\n", "DASSCM_PASSWORD: (hidden)\n", "DASSCM_PROD: $DASSCM_PROD\n", "DASSCM_REPO: $DASSCM_REPO\n", "Server Repository: $DASSCM_SVN_REPOSITORY\n", "\n", "[dasscm shell]\n\n"; exec("bash") or die "failed to start new shell"; } sub init(@) { check_parameter( @_, 1 ); check_env(); # update complete repository my $retcode = run_interactive( "cd $DASSCM_LOCAL_REPOSITORY_BASE; $SVN checkout $svnCheckoutCredentials $svnOptions $DASSCM_SVN_REPOSITORY" ); } # # add (is used for command add and commit) # sub add(@) { check_parameter( @_, 1 ); check_env(); ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); if ( $command eq "add" ) { mkpath($dirname_repo); } # update complete repository my $retcode = run_interactive("$SVN update $svnOptions $DASSCM_REPO"); copy( $filename_prod, $filename_repo ) or die $!; if ( $command eq "add" ) { # already checked in? chdir($DASSCM_REPO); # also add the path to filename. for my $dir ( split( '/', $dirname_prod ) ) { if ($dir) { run_command("$SVN add --non-recursive $dir"); chdir $dir; } } run_command("$SVN add $basename"); } if ( $options{'message'} ) { $svnOptions .= " --message \"$options{'message'}\" "; } # commit calls $EDITOR. uses "interactive" here, to display output $retcode = run_interactive( "$SVN commit $svnOptions --username $DASSCM_USERNAME $svnPasswordCredentials $DASSCM_REPO" ); print $filename_prod. "\n"; print $dirname_repo. "\n"; } sub blame(@) { check_parameter( @_, 1 ); check_env(); ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); my $retcode = run_interactive("$SVN blame $svnOptions $filename_repo"); } sub diff(@) { check_parameter( @_, 1 ); check_env(); ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); #print "$basename,$dirname_prod,$dirname_repo\n"; ( my $rc_update, my @result ) = run_command("$SVN update $filename_repo"); if ( $rc_update != 0 ) { print @result; die; } ( my $rc_diff, my @diff ) = run_command("diff $filename_repo $filename_prod"); print @diff; } sub status(@) { check_parameter( @_, 1 ); check_env(); # # update local repository # svn_update(); # TODO: start at subdirectories ? my $cvsworkdir = $DASSCM_REPO; File::Find::find( \&cvscheck, $cvsworkdir ); # Liste der geänderten Files ausgeben, falls nicht leer # Anzahl Elemente im Hash??? my @changedfiles = keys %status_changedfiles; if ( %status_changedfiles or %status_removedfiles ) { if (%status_removedfiles) { print "deleted files:\n"; foreach my $key ( values %status_removedfiles ) { print "$key\n"; } print "\n"; } if (%status_changedfiles) { print "modified files:\n"; foreach my $key ( keys %status_changedfiles ) { print "$key\n"; } } } else { print "no modified files found in $cvsworkdir\n"; } print "\n"; } ##################################################################### # # main # my $number_arguments = @ARGV; if ( $number_arguments > 0 ) { # get subcommand and remove it from @ARGV $command = $ARGV[0]; shift @ARGV; $DASSCM_LOCAL_REPOSITORY_BASE = $config->{'DASSCM_LOCAL_REPOSITORY_BASE'}; $DASSCM_REPOSITORY_NAME = $config->{'DASSCM_REPOSITORY_NAME'}; # TODO: check variables $DASSCM_SVN_REPOSITORY = $config->{'DASSCM_SVN_REPOSITORY_BASE'} . "/" . $DASSCM_REPOSITORY_NAME; my $DASSCM_CHECKOUT_USERNAME = $config->{'DASSCM_CHECKOUT_USERNAME'}; my $DASSCM_CHECKOUT_PASSWORD = $config->{'DASSCM_CHECKOUT_PASSWORD'}; # # if a user is given by dasscm configuration file, we use it. # Otherwise we expect that read-only account is configured # as local subversion configuration. # If this is also not the case, # user is required to type username and password. # This will be stored as local subversion configuration thereafter. # if ( $DASSCM_CHECKOUT_USERNAME && $DASSCM_CHECKOUT_PASSWORD ) { $svnCheckoutCredentials = " --username $DASSCM_CHECKOUT_USERNAME --password $DASSCM_CHECKOUT_PASSWORD "; } # get command line options and store them in options hash my $result = GetOptions( \%options, 'message=s' ); # print options foreach my $option ( keys %options ) { print $option. ": " . $options{$option} . "\n"; } $_ = $command; if (m/help/i) { help(@ARGV); } elsif (m/login/i) { $command = "login"; login(@ARGV); } elsif (m/init/i) { $command = "init"; init(@ARGV); } elsif (m/add/i) { ## rewrite command $command = "add"; add(@ARGV); } elsif (m/commit/i) { $command = "commit"; add(@ARGV); } elsif (m/blame/i) { $command = "blame"; blame(@ARGV); } elsif (m/diff/i) { $command = "diff"; diff(@ARGV); } elsif (m/status/i) { $command = "status"; status(@ARGV); } elsif (m/activate/i) { ## TODO activate(@ARGV); } else { usage(); check_env(); } # up # cleanup (svn-commit.tmp) # commitall # revert # status (chkconf) }