#!/usr/bin/perl -w # Name: pc_fix_svn_properties # Author: wd (wdobler [at] gmail [dot] com) # Date: 08-Dec-2008 # Description: # For all files containing a SVN keywords line, set the corresponding # svn:keywords property # So far, we handle only Id, which is probably enough. # Usage: # pc_fix_svn_properties [-nq] dir1|file1 [dir2|file2 [...]] # pc_fix_svn_properties [-v|-h] # Options: # -n, --dry-run Don't change any properties, just print what would happen # -h, --help This help # -v, --version Print version number # -q, --quiet Be quiet # Arguments: # Arguments are either names of files to fix or names of directories to # traverse recursively, fixing the properties for all files. # Examples: # pc_fix_svn_properties README # pc_fix_svn_properties . # pc_fix_svn_properties ${PENCIL_HOME} # Copyright (C) 2008 Wolfgang Dobler # # This program is free software; you can redistribute it and/or modify it # under the same conditions as Perl or under the GNU General Public # License, version 3 or later. use strict; use Getopt::Long; # Allow for `-Plp' as equivalent to `-P lp' etc: Getopt::Long::config("bundling"); my (%opts); # Options hash for GetOptions my $doll='\$'; # Need this to trick CVS ## Process command line GetOptions(\%opts, qw( -n --dry-run -h --help --debug -q --quiet -v --version ) ) or die "Aborting.\n"; my $debug = ($opts{'debug'} ? 1 : 0 ); # undocumented debug option if ($debug) { printopts(\%opts); print "\@ARGV = `@ARGV'\n"; } if ($opts{'h'} || $opts{'help'}) { die usage(); } if ($opts{'v'} || $opts{'version'}) { die version(); } my $dry_run = ($opts{'n'} || $opts{'dry-run'} || ''); my $quiet = ($opts{'q'} || $opts{'quiet'} || ''); my @files = @ARGV; die usage() unless (@files); my ($count, $no_kw_count, $already_set) = (0, 0, 0); for my $file_or_dir (@files) { $count += fix_file_or_dir($file_or_dir); } unless ($quiet) { print "Fixed SVN properties for $count files\n"; print "No keywords in $no_kw_count files\n"; print "`Id' already set in $already_set files\n"; } # ---------------------------------------------------------------------- # sub fix_file_or_dir { # Fix SVN properties for given file, or recursively for all files in given # directory my ($file_or_dir) = @_; if (-f $file_or_dir) { # plain file return fix_file($file_or_dir); } elsif (-d $file_or_dir) { # directory return fix_dir($file_or_dir); } elsif (-l $file_or_dir) { # symbolic link return 0; } elsif (! -e $file_or_dir) { # nonexistent warn "No such file or directory: $file_or_dir\n"; } else { # other type warn "$file_or_dir is neither file, nor directory\n"; } return 0; } # ---------------------------------------------------------------------- # sub fix_file { # Fix SVN properties for given file my ($file) = @_; my $status = `svn status $file`; # output from `svn status' is presumably column oriented, but not # really: no output means file is under SVN and has no changes. Argh. "$status " =~ /(.).*/; my $indicator = $1; # Known no-op cases if ($indicator =~ /[I?]/) { return 0; } if ($indicator =~ /[C]/) { warn "Cowardly refusing to operate on conflicting file"; return 0; } # Unknown cases if ($indicator !~ /[ AMR]/) { warn "$file does not appear to be under SVN"; return 0; } return fix_svn_file($file); } # ---------------------------------------------------------------------- # sub fix_dir { # Fix SVN properties for all files or directories (recursively) in given # directory that are under SVN. my ($dir) = @_; warn "fix_dir: \$dir=<$dir>\n" if ($debug); use File::Find; my @files; find( sub { # Skip VC directories (in particular .svn/) if ($_ =~ /^(\.svn|\.hg|_darcs|\.git)$/) { $File::Find::prune = 1; warn "Pruning dirs: $File::Find::name\n" if ($debug); return 0; } # Skip backup files (shouldn't be under SVN anyway) if ($_ =~ /(~|\.bak)$/) { warn "Skipping backups: $File::Find::name\n" if ($debug); return 0; } # Skip files or dirs not under SVN unless (svn_controlled($_)) { $File::Find::prune = 1; warn "Not under SVN: $File::Find::name\n" if ($debug); return 0; } # Dirs are not interesting on their own if (-d $_) { warn "Directory: $File::Find::name\n" if ($debug); return; } if (-f $_) { push @files, $File::Find::name; } else { warn "Don't know what to do with $File::Find::name\n"; } }, $dir ); my $count = 0; for my $file (@files) { $count += fix_svn_file($file); } return $count; } # ---------------------------------------------------------------------- # sub fix_svn_file { # Fix SVN properties for file that is known to be under SVN my ($file) = @_; if (grep /^\s*svn:keywords\s*$/, `svn proplist $file`) { my $prop = `svn propget svn:keywords $file`; chomp $prop; if ($prop eq 'Id') { warn "$file: property `$prop' is already there\n" if ($debug); $already_set++; return 0; } else { warn "$file has svn:keywords=`$prop' != `Id' -- not fixing\n"; return 0; } } unless (open(FILE, "<$file")) { warn "Cannot open $file for reading\n"; return 0; } my $found = 0; while (defined(my $line = )) { if ($line =~ /\$Id($|:)/) { $found = 1; last; } } close FILE; if ($found) { print "Fixing $file\n" unless ($quiet); my $new_prop = 'Id'; system('svn', 'propset', 'svn:keywords', $new_prop, $file) unless ($dry_run); return 1; } else { warn "No keywords in $file\n" if ($debug); $no_kw_count++; return 0; } } # ---------------------------------------------------------------------- # sub svn_controlled { # Return 1 if file is under SVN, 0 otherwise my ($file) = @_; if (`svn status --depth empty $file` =~ /^\?/) { return 0; } else { return 1; } } # ---------------------------------------------------------------------- # sub printopts { # Print command line options my $optsref = shift; my %opts = %$optsref; foreach my $opt (keys(%opts)) { print STDERR "\$opts{$opt} = `$opts{$opt}'\n"; } } # ---------------------------------------------------------------------- # sub usage { # Extract description and usage information from this file's header. my $thisfile = __FILE__; local $/ = ''; # Read paragraphs open(FILE, "<$thisfile") or die "Cannot open $thisfile\n"; while () { # Paragraph _must_ contain `Description:' or `Usage:' next unless /^\s*\#\s*(Description|Usage):/m; # Drop `Author:', etc. (anything before `Description:' or `Usage:') s/.*?\n(\s*\#\s*(Description|Usage):\s*\n.*)/$1/s; # Don't print comment sign: s/^\s*# ?//mg; last; # ignore body } return $_ or "\n"; } # ---------------------------------------------------------------------- # sub version { # Return CVS/SVN data and version info. my $doll='\$'; # Need this to trick CVS/SVN my $cmdname = (split('/', $0))[-1]; my $rev = '$Revision: 1.12 $'; my $date = '$Date: 2008/07/07 21:37:16 $'; $rev =~ s/${doll}Revision:\s*(\S+).*/$1/; $date =~ s/${doll}Date:\s*(\S+).*/$1/; return "$cmdname version $rev ($date)\n"; } # ---------------------------------------------------------------------- # # End of file [name] # End of file pc_fix_svn_properties