source: trunk/dasscm/dasscm@ 233

Last change on this file since 233 was 233, checked in by joergs, on Oct 8, 2008 at 3:45:21 PM

better check file types. Don't try to checkin directories, because they fail. Offer checkin of multiple files

  • Property keyword set to id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 20.2 KB
Line 
1#!/usr/bin/perl -w
2
3# $Id: dasscm 233 2008-10-08 13:45:21Z joergs $
4
5use strict;
6
7use Env
8 qw($DASSCM_PROD $DASSCM_REPO $USER $DASSCM_USERNAME $DASSCM_USER $DASSCM_PASSWORD);
9use Cwd;
10use Getopt::Long;
11use File::Basename;
12use File::Compare;
13use File::Copy;
14use File::stat;
15use File::Path;
16use Term::ReadKey;
17
18#
19# used ConfigFile instead of SmartClient::Config,
20# because the huge amount of SmartClient dependencies
21#use SmartClient::Config;
22use ConfigFile;
23
24#####################################################################
25#
26# global
27#
28
29# file to store permissions
30my $permissions_file = "/etc/permissions.d/dasscm.permission_backup";
31
32# configuration file
33my $config_file = "/etc/dasscm.conf";
34my $config = ConfigFile::read_config_file($config_file);
35my $DASSCM_LOCAL_REPOSITORY_BASE;
36my $DASSCM_REPOSITORY_NAME;
37my $DASSCM_SVN_REPOSITORY;
38
39my $SVN = "svn ";
40my $svnOptions = "";
41my $svnCheckoutCredentials = "";
42my $svnPasswordCredentials = "";
43
44# command line options get stored in options hash
45my %options = ();
46
47# subcommand, that gets executed (add, commit, ...)
48my $command;
49
50my $verbose = 0;
51
52#####################################################################
53#
54# util functions
55#
56sub usage()
57{
58 print "usage: dasscm <subcommand> [options] [args]\n";
59 print "\n";
60 print "dasscm is intended to help versioning configuration files\n";
61 print "\n";
62 print "Available subcommands:\n";
63 print " help <subcommand>\n";
64 print " init\n";
65 print " login\n";
66 print " up\n";
67 print " ls\n";
68 print " add <filename>\n";
69 print " commit <filename>\n";
70 print " status <filename>\n";
71 print " diff <filename>\n";
72 print " permissions\n";
73 print "\n";
74 print "preperation:\n", " if dasscm is already configured,\n",
75 " use 'dasscm login' and than eg. 'add'.\n",
76 " The environment variables\n", " DASSCM_REPO\n", " DASSCM_PROD\n",
77 " DASSCM_USERNAME\n", " DASSCM_PASSWORD\n",
78 " are evaluated, but set automatically by 'dasscm login'.\n", "\n",
79 " If dasscm is not yet configured, read",
80 " /usr/share/doc/packages/dasscm/dasscm_howto.txt\n";
81}
82
83sub warning(@)
84{
85 print "Warning: " . join( "\n ", @_ ) . "\n";
86}
87
88sub error(@)
89{
90 print "Error: " . join( "\n ", @_ ) . "\n";
91}
92
93sub fatalerror(@)
94{
95 error( @_ );
96 #print "Exiting\n";
97 exit 1
98}
99
100sub check_env()
101{
102
103 # DASSCM_PROD
104 if ( !$DASSCM_PROD ) {
105 $DASSCM_PROD = "/";
106 }
107
108 if ( !-d $DASSCM_PROD ) {
109 die "DASSCM_PROD ($DASSCM_PROD) is not set to a directory.\n";
110 }
111 if ($verbose) { print "DASSCM_PROD: " . $DASSCM_PROD . "\n"; }
112
113 # DASSCM_REPOSITORY_NAME
114 if ( !$DASSCM_REPOSITORY_NAME ) {
115 die
116 "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";
117 }
118
119 # DASSCM_REPO
120 if ( !$DASSCM_REPO ) {
121 if ( $DASSCM_LOCAL_REPOSITORY_BASE && $DASSCM_REPOSITORY_NAME ) {
122 $DASSCM_REPO =
123 $DASSCM_LOCAL_REPOSITORY_BASE . "/" . $DASSCM_REPOSITORY_NAME;
124 } else {
125 die
126 "Envirnonment variable DASSCM_REPO not set.\nSet DASSCM_REPO to the directory of the versioning system checkout for this machine.\n";
127 }
128 }
129 if ($verbose) { print "DASSCM_REPO: " . $DASSCM_REPO . "\n"; }
130
131 #
132 # check if local repository directory exist (if not creating by init)
133 #
134 if ( $command ne "init" ) {
135 if ( not -d $DASSCM_REPO ) {
136 die
137 "Can't access local repository DASSCM_REPO\n($DASSCM_REPO)\nCheck configuration and execute\n dasscm init\n";
138 }
139
140 #
141 # user settings
142 #
143
144 # DASSCM_USER is legacy. Use DASSCM_USERNAME instead
145 if ( !$DASSCM_USERNAME ) {
146 $DASSCM_USERNAME = $DASSCM_USER;
147 }
148
149 # user root is not allowed for checkins.
150 # if user is root, DASSCM_USER has to be set,
151 # otherwise USER can be used
152 if ( "$USER" eq "root" ) {
153 if ( ( not $DASSCM_USERNAME ) and ( $command ne "login" ) ) {
154 die
155 "Envirnonment variable DASSCM_USERNAME not set.\nSet DASSCM_USERNAME to your subversion user account.\n";
156 }
157 $svnOptions .= " --no-auth-cache ";
158 } elsif ( !$DASSCM_USERNAME ) {
159 $DASSCM_USERNAME = $USER;
160 }
161
162 #
163 # password
164 #
165 if ($DASSCM_PASSWORD) {
166 $svnPasswordCredentials = " --password $DASSCM_PASSWORD ";
167 }
168 }
169
170 #$svnOptions .= " --username $DASSCM_USERNAME "
171}
172
173sub check_parameter(@)
174{
175 if( not @_ ) {
176 fatalerror( "no files specified. See 'dasscm --help'" );
177 }
178}
179
180sub get_filenames(@)
181{
182 my $filename_prod = $_[0];
183 if ( !( $filename_prod =~ m/^\// ) ) {
184 $filename_prod = cwd() . "/" . $filename_prod;
185 }
186
187 if( not -r $filename_prod ) {
188 fatalerror( $filename_prod . " is not accessable" );
189 } elsif( -l $filename_prod ) {
190 my $dest = readlink($filename_prod);
191 # TODO:
192 # default: disallow, but offer cmd switch to activate
193 # (or check, if file is already checked in and has been a link before)
194 warning( "'$filename_prod' is a link to '$dest'.", "Please check, if '$dest' should be stored in repository instead" );
195 if( ! -f $dest ) {
196 fatalerror( "link target '$dest' is not a regular file. Giving up" );
197 }
198 } elsif( ! -f $filename_prod ) {
199 fatalerror( $filename_prod . " is not a regular file. Only regular files can be checked in." );
200 }
201
202 # TODO: dirname buggy: eg. "/etc/" is reduced to "/",
203 # "/etc" is used as filename
204 my $dirname_prod = dirname($filename_prod);
205 chdir $dirname_prod or die $!;
206 $dirname_prod = cwd();
207 my $basename = basename($filename_prod);
208
209 if ($verbose) {
210 print "dir: " . $dirname_prod . "\n";
211 print "fn: " . $basename . "\n";
212 }
213
214 my $dirname_repo = $DASSCM_REPO . "/" . $dirname_prod;
215 my $filename_repo = "$dirname_repo/$basename";
216
217 return (
218 $basename, $dirname_prod, $dirname_repo,
219 $filename_prod, $filename_repo
220 );
221}
222
223sub generatePermissionList
224{
225
226 # generieren der Zeilen für Permission-Savefile
227 my @files = @_;
228 my @permlist = ();
229 foreach my $file (@files) {
230 $file = "/" . $file;
231 if( -e $file ) {
232 my $info = stat( $file ) || die "failed to stat $file: aborting";
233 my $mode = get_type( $info->mode ) & 07777;
234 my $modestring = sprintf( "%04o", $mode );
235 my $uid = $info->uid;
236 my $uidname = getpwuid($uid);
237 my $gid = $info->gid;
238 my $gidname = getgrgid($gid);
239 push(
240 @permlist,
241 sprintf( "%-55s %-17s %4d",
242 $file, "${uidname}:${gidname}", $modestring )
243 );
244 } else {
245 print "failed to get status of $file. It exists in the repository, but not on the system\n";
246 }
247 }
248 return @permlist;
249}
250
251sub get_type
252{
253
254 # Funktion übernommen aus /usr/bin/chkstat
255 my $S_IFLNK = 0120000; # symbolic link
256 my $S_IFREG = 0100000; # regular file
257 my $S_IFDIR = 0040000; # directory
258 my $S_IFCHAR = 0020000; # character device
259 my $S_IFBLK = 0060000; # block device
260 my $S_IFFIFO = 0010000; # fifo
261 my $S_IFSOCK = 0140000; # socket
262 my $S_IFMT = 0170000; # type of file
263
264 my $S_m;
265 if ( ( $_[0] & $S_IFMT ) == $S_IFLNK ) { $S_m = $_[0] - $S_IFLNK; }
266 elsif ( ( $_[0] & $S_IFMT ) == $S_IFREG ) { $S_m = $_[0] - $S_IFREG; }
267 elsif ( ( $_[0] & $S_IFMT ) == $S_IFDIR ) { $S_m = $_[0] - $S_IFDIR; }
268 elsif ( ( $_[0] & $S_IFMT ) == $S_IFCHAR ) { $S_m = $_[0] - $S_IFCHAR; }
269 elsif ( ( $_[0] & $S_IFMT ) == $S_IFBLK ) { $S_m = $_[0] - $S_IFBLK; }
270 elsif ( ( $_[0] & $S_IFMT ) == $S_IFFIFO ) { $S_m = $_[0] - $S_IFFIFO; }
271 elsif ( ( $_[0] & $S_IFMT ) == $S_IFSOCK ) { $S_m = $_[0] - $S_IFSOCK; }
272 $S_m;
273}
274
275sub run_command
276{
277 my $command = shift;
278
279 #print "executing command: " . $command . "\n";
280
281 open( RESULT, $command . ' 2>&1 |' );
282 my @result = <RESULT>;
283 close(RESULT);
284 my $retcode = $? >> 8;
285
286 #print @result;
287 #if( $retcode ) { print "return code: " . $retcode . "\n"; }
288
289 return ( $retcode, @result );
290}
291
292sub run_interactive
293{
294
295 if ($verbose) {
296 print "run_interactive:" . join( " ", @_ ) . "\n";
297 }
298
299 system(@_);
300 if ( $? == -1 ) {
301 printf "failed to execute: $!\n";
302 } elsif ( $? & 127 ) {
303 printf "child died with signal %d, %s coredump\n", ( $? & 127 ),
304 ( $? & 128 ) ? 'with' : 'without';
305 } elsif ( $? >> 8 != 0 ) {
306 printf "child exited with value %d\n", $? >> 8;
307 }
308 return ( $? >> 8 );
309}
310
311sub svn_check_credentials( $$ )
312{
313 my $username = shift;
314 my $password = shift;
315
316 print "checking credentials ... ";
317
318 # Options for "svn info" are not supported by subversion 1.0.0 (SLES9),
319 # therefore switching to "svn status"
320 # ( my $rc_update, my @result ) =
321 # run_command(
322 # "$SVN info --non-interactive --no-auth-cache --username $username --password $password $DASSCM_SVN_REPOSITORY"
323 # );
324 #print @result;
325
326 ( my $rc_update, my @result ) =
327 run_command(
328 "$SVN ls --non-interactive --no-auth-cache --username $username --password $password $DASSCM_SVN_REPOSITORY"
329 );
330
331 if ( $rc_update != 0 ) {
332 print @result;
333 die;
334 }
335
336}
337
338sub svn_update( ;$ )
339{
340 my $update_path = shift || $DASSCM_REPO;
341 ( my $rc_update, my @result ) =
342 run_command(
343 "$SVN update --non-interactive $svnCheckoutCredentials $update_path");
344 print @result;
345 if ( $rc_update != 0 ) {
346 die;
347 }
348}
349
350sub svn_getStoredFiles( ;$ )
351{
352
353 # TODO: get_filenames?
354 #my $rel_path = shift || "";
355 #my $path = "${DASSCM_REPO}/${rel_path}";
356 my $path = ${DASSCM_REPO};
357
358 # svn ls -R is better, but much, much slower
359 # ( my $rc, my @result ) = run_command("$SVN ls --recursive $svnCheckoutCredentials $path");
360 ( my $rc, my @result ) =
361 run_command(
362 "cd $path && find | grep -v '/.svn' | sed -e 's/\.\\///' | grep -v '^\$'"
363 );
364 if ( $rc != 0 ) {
365 print @result;
366 die;
367 }
368 chomp(@result);
369 return @result;
370}
371
372#####################################################################
373#
374# functions
375
376sub help(;@)
377{
378 if ( @_ == 0 ) {
379 usage();
380 } else {
381 print "help for @_: ...\n";
382 usage();
383 }
384}
385
386sub login(@)
387{
388 check_parameter( @_, 1 );
389 check_env();
390
391 my $input_username = $1;
392
393 if ( not $input_username ) {
394 my $output_username = "";
395 if ($DASSCM_USERNAME) {
396 $output_username = " ($DASSCM_USERNAME)";
397 }
398
399 print "Enter DASSCM user name", $output_username, ": ";
400 $input_username = <STDIN>;
401 chomp($input_username);
402 }
403
404 # hidden password input
405 print "Enter DASSCM user password: ";
406 ReadMode('noecho');
407 my $input_password = <STDIN>;
408 ReadMode('normal');
409 chomp($input_password);
410 print "\n";
411
412 svn_check_credentials( $input_username, $input_password );
413
414 #
415 # set environment variables
416 #
417 $ENV{'DASSCM_USERNAME'} = $input_username;
418 $ENV{'DASSCM_PASSWORD'} = $input_password;
419
420 print "subversion access okay\n\n", "DASSCM_USERNAME: $input_username\n",
421 "DASSCM_PASSWORD: (hidden)\n", "DASSCM_PROD: $DASSCM_PROD\n",
422 "DASSCM_REPO: $DASSCM_REPO\n",
423 "Server Repository: $DASSCM_SVN_REPOSITORY\n", "\n", "[dasscm shell]\n\n";
424
425 exec("bash") or die "failed to start new shell";
426}
427
428sub init(@)
429{
430 check_parameter( @_, 1 );
431 check_env();
432
433 # update complete repository
434 # and create permission file
435 my $retcode =
436 run_interactive(
437 "cd $DASSCM_LOCAL_REPOSITORY_BASE; $SVN checkout $svnCheckoutCredentials $svnOptions $DASSCM_SVN_REPOSITORY; touch $permissions_file"
438 );
439}
440
441sub ls(@)
442{
443 check_parameter( @_, 1 );
444 check_env();
445
446 my @files = svn_getStoredFiles(@_);
447
448 print join( "\n", @files );
449 print "\n";
450}
451
452sub update(@)
453{
454 check_parameter( @_, 1 );
455 check_env();
456
457 #
458 # update local repository
459 #
460 svn_update();
461}
462
463sub add_helper(@)
464{
465 (
466 my $basename,
467 my $dirname_prod,
468 my $dirname_repo,
469 my $filename_prod,
470 my $filename_repo
471 )
472 = get_filenames( $_[0] );
473
474 if ( $command eq "add" ) {
475 mkpath($dirname_repo);
476 }
477
478 copy( $filename_prod, $filename_repo ) or die "failed to copy $filename_prod to repository: $!";
479
480 if ( $command eq "add" ) {
481
482 # already checked in?
483 chdir($DASSCM_REPO);
484
485 # also add the path to filename.
486 for my $dir ( split( '/', $dirname_prod ) ) {
487 if ($dir) {
488 my( $rc, @out ) = run_command("$SVN add --non-recursive \"" . $dir . "\"" );
489 if( $rc > 0 ) {
490 print join( "\n", @out );
491 }
492 chdir $dir;
493 }
494 }
495 my( $rc, @out ) = run_command("$SVN add \"" . $basename . "\"");
496 if( $rc > 0 ) {
497 print join( "\n", @out );
498 }
499 }
500}
501
502#
503# add (is used for command add and commit)
504#
505sub add(@)
506{
507 check_parameter( @_, 1 );
508 check_env();
509
510 #
511 # update local repository
512 #
513 svn_update();
514
515 # TODO: check all files
516
517 for my $file (@_) {
518 # add file
519 add_helper( $file );
520 }
521
522 # create new permissions file
523 permissions();
524
525 # add permissions file
526 add_helper($permissions_file);
527
528 if ( $options{'message'} ) {
529 $svnOptions .= " --message \"$options{'message'}\" ";
530 }
531
532 # commit calls $EDITOR. uses "interactive" here, to display output
533 my $retcode =
534 run_interactive(
535 "$SVN commit $svnOptions --username $DASSCM_USERNAME $svnPasswordCredentials $DASSCM_REPO"
536 );
537
538 #print $filename_prod. "\n";
539 #print $dirname_repo. "\n";
540}
541
542sub blame(@)
543{
544 check_parameter( @_, 1 );
545 check_env();
546
547 (
548 my $basename,
549 my $dirname_prod,
550 my $dirname_repo,
551 my $filename_prod,
552 my $filename_repo
553 )
554 = get_filenames( $_[0] );
555
556 my $retcode = run_interactive("$SVN blame $svnOptions $filename_repo");
557}
558
559sub diff(@)
560{
561 check_parameter( @_, 1 );
562 check_env();
563
564 (
565 my $basename,
566 my $dirname_prod,
567 my $dirname_repo,
568 my $filename_prod,
569 my $filename_repo
570 )
571 = get_filenames( $_[0] );
572
573 #print "$basename,$dirname_prod,$dirname_repo\n";
574
575 ( my $rc_update, my @result ) = run_command("$SVN update $filename_repo");
576 if ( $rc_update != 0 ) {
577 print @result;
578 die;
579 }
580
581 ( my $rc_diff, my @diff ) =
582 run_command("diff $filename_repo $filename_prod");
583 print @diff;
584}
585
586sub status(@)
587{
588 check_parameter( @_, 1 );
589 check_env();
590
591 #
592 # update local repository
593 #
594 svn_update();
595
596 # TODO: start at subdirectories ?
597 my $dir = $DASSCM_REPO;
598 my @files = svn_getStoredFiles($dir);
599
600 # Liste der geänderten Files ausgeben, falls nicht leer
601 if (@files) {
602
603 # stores result from status (cvscheck)
604 my %removedfiles = ();
605 my %changedfiles = ();
606
607 foreach my $file (@files) {
608
609 my $realfile = "/" . $file;
610 my $cvsworkfile = "${DASSCM_REPO}/${file}";
611
612 if ( -d $realfile ) {
613
614 # directory. do nothing
615 } elsif ( !-r $realfile ) {
616 $removedfiles{"$realfile"} = $cvsworkfile;
617 } else {
618 ( -r "$cvsworkfile" )
619 || die("Fehler: $cvsworkfile ist nicht lesbar");
620 if ( compare( $cvsworkfile, $realfile ) != 0 ) {
621 $changedfiles{"$realfile"} = $cvsworkfile;
622 }
623 }
624 }
625
626 if (%removedfiles) {
627 print "deleted files (found in repository, but not in system):\n";
628 foreach my $key ( values %removedfiles ) {
629 print "$key\n";
630 }
631 print "\n";
632 }
633
634 if (%changedfiles) {
635 print "modified files:\n";
636 foreach my $key ( keys %changedfiles ) {
637 print "$key\n";
638 }
639 }
640 } else {
641 print "no modified files found in $dir\n";
642 }
643
644 print "\n";
645}
646
647sub permissions(@)
648{
649 check_parameter( @_, 1 );
650 check_env();
651
652 #
653 # update local repository
654 #
655 #svn_update();
656
657 # TODO: start at subdirectories ?
658 my $dir = $DASSCM_REPO;
659 my @files = svn_getStoredFiles($dir);
660
661 if (@files) {
662
663 # generieren der Permissions
664 my @permissions = generatePermissionList(@files);
665 my $OUTFILE;
666 my $tofile = 0; # Status für schreiben in File
667
668 if ( -w dirname($permissions_file) ) {
669
670 # Verzeichnis existiert => schreiben
671 print "storing permissions in file $permissions_file\n";
672 open( OUTFILE, ">$permissions_file" )
673 || die("failed to write to $permissions_file: $!");
674 $tofile = 1; # Merken, daß in File geschrieben wird
675 print OUTFILE "#\n";
676 print OUTFILE "# created by dasscm permissions\n";
677 print OUTFILE
678 "# It is intended to be used for restoring permissions\n";
679 } else {
680
681 # Pfad für Sicherungsdatei existiert nicht => schreiben auf stdout
682 # Alias Filehandle für stdout erzeugen
683 *OUTFILE = *STDOUT;
684 }
685 foreach my $line (@permissions) {
686 print OUTFILE "$line\n";
687 }
688
689 if ($tofile) {
690 close(OUTFILE);
691 }
692 }
693}
694
695#####################################################################
696#
697# main
698#
699
700my $number_arguments = @ARGV;
701
702if ( $number_arguments > 0 ) {
703
704 # get subcommand and remove it from @ARGV
705 $command = $ARGV[0];
706 shift @ARGV;
707
708 $DASSCM_LOCAL_REPOSITORY_BASE = $config->{'DASSCM_LOCAL_REPOSITORY_BASE'};
709 $DASSCM_REPOSITORY_NAME = $config->{'DASSCM_REPOSITORY_NAME'};
710
711 # TODO: check variables
712 $DASSCM_SVN_REPOSITORY =
713 $config->{'DASSCM_SVN_REPOSITORY_BASE'} . "/" . $DASSCM_REPOSITORY_NAME;
714
715 my $DASSCM_CHECKOUT_USERNAME = $config->{'DASSCM_CHECKOUT_USERNAME'};
716 my $DASSCM_CHECKOUT_PASSWORD = $config->{'DASSCM_CHECKOUT_PASSWORD'};
717
718 #
719 # if a user is given by dasscm configuration file, we use it.
720 # Otherwise we expect that read-only account is configured
721 # as local subversion configuration.
722 # If this is also not the case,
723 # user is required to type username and password.
724 # This will be stored as local subversion configuration thereafter.
725 #
726 if ( $DASSCM_CHECKOUT_USERNAME && $DASSCM_CHECKOUT_PASSWORD ) {
727 $svnCheckoutCredentials =
728 " --username $DASSCM_CHECKOUT_USERNAME --password $DASSCM_CHECKOUT_PASSWORD ";
729 }
730
731 # get command line options and store them in options hash
732 my $result = GetOptions( \%options, 'verbose', 'message=s' );
733
734 # print options
735 foreach my $option ( keys %options ) {
736 print "${option}: $options{$option}\n";
737 }
738
739 # set verbose to command line option
740 $verbose = $options{'verbose'};
741
742 #
743 # action accordinly to command are taken
744 # $command is rewritten in standard format,
745 # so we can test for it later on more simply
746 #
747 $_ = $command;
748 if (m/help/i) {
749 help(@ARGV);
750 } elsif (m/login/i) {
751 $command = "login";
752 login(@ARGV);
753 } elsif (m/init/i) {
754 $command = "init";
755 init(@ARGV);
756 } elsif (m/ls/i) {
757 $command = "ls";
758 ls(@ARGV);
759 } elsif (m/up/i) {
760 $command = "update";
761 update(@ARGV);
762 } elsif (m/add/i) {
763 $command = "add";
764 add(@ARGV);
765 } elsif (m/commit/i) {
766 $command = "commit";
767 add(@ARGV);
768 } elsif (m/blame/i) {
769 $command = "blame";
770 blame(@ARGV);
771 } elsif (m/diff/i) {
772 $command = "diff";
773 diff(@ARGV);
774 } elsif (m/status/i) {
775 $command = "status";
776 status(@ARGV);
777 } elsif (m/permissions/i) {
778 $command = "permissions";
779 permissions(@ARGV);
780 } else {
781 print "unknown command: $command\n\n";
782 usage();
783 check_env();
784 }
785
786 # cleanup (svn-commit.tmp)
787 # commitall
788 # revert
789 # activate
790 # rm
791}
Note: See TracBrowser for help on using the repository browser.