#!/usr/bin/env perl # ==================================================================== # check-mime-type.pl: check that every added or property-modified file # has the svn:mime-type property set and every added or property-modified # file with a mime-type matching text/* also has svn:eol-style set. # If any file fails this test the user is sent a verbose error message # suggesting solutions and the commit is aborted. # # Usage: check-mime-type.pl REPOS TXN-NAME # ==================================================================== # Most of check-mime-type.pl was taken from # commit-access-control.pl, Revision 9986, 2004-06-14 16:29:22 -0400. # ==================================================================== # Copyright (c) 2000-2009 CollabNet. All rights reserved. # Copyright (c) 2010-2020 Apache Software Foundation (ASF). # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ==================================================================== # Turn on warnings the best way depending on the Perl version. BEGIN { if ( $] >= 5.006_000) { require warnings; import warnings; } else { $^W = 1; } } use strict; use Carp; ###################################################################### # Configuration section. # Toggle: Check files of mime-type text/* for svn:eol-style property. my $check_text_eol = 1; # Toggle: Check property-modified files too. my $check_prop_modified_files = 0; # Svnlook path. my $svnlook = "/usr/bin/svnlook"; # Since the path to svnlook depends upon the local installation # preferences, check that the required program exists to insure that # the administrator has set up the script properly. { my $ok = 1; foreach my $program ($svnlook) { if (-e $program) { unless (-x $program) { warn "$0: required program `$program' is not executable, ", "edit $0.\n"; $ok = 0; } } else { warn "$0: required program `$program' does not exist, edit $0.\n"; $ok = 0; } } exit 1 unless $ok; } ###################################################################### # Initial setup/command-line handling. &usage unless @ARGV == 2; my $repos = shift; my $txn = shift; unless (-e $repos) { &usage("$0: repository directory `$repos' does not exist."); } unless (-d $repos) { &usage("$0: repository directory `$repos' is not a directory."); } # Define two constant subroutines to stand for read-only or read-write # access to the repository. sub ACCESS_READ_ONLY () { 'read-only' } sub ACCESS_READ_WRITE () { 'read-write' } ###################################################################### # Harvest data using svnlook. # Change into /tmp so that svnlook diff can create its .svnlook # directory. my $tmp_dir = '/tmp'; chdir($tmp_dir) or die "$0: cannot chdir `$tmp_dir': $!\n"; # Figure out what files have been added/property-modified using svnlook. my $regex_files_to_check; if ($check_prop_modified_files) { $regex_files_to_check = qr/^(?:A.|.U) (.*[^\/])$/; } else { $regex_files_to_check = qr/^A. (.*[^\/])$/; } my @files_to_check; foreach my $line (&read_from_process($svnlook, 'changed', $repos, '-t', $txn)) { # Add only files that were added/property-modified to @files_to_check if ($line =~ /$regex_files_to_check/) { push(@files_to_check, $1); } } my @errors; foreach my $path ( @files_to_check ) { my $mime_type; my $eol_style; # Parse the complete list of property values of the file $path to extract # the mime-type and eol-style my @output = &read_from_process($svnlook, 'proplist', $repos, '-t', $txn, '--verbose', '--', $path); my $output_line = 0; foreach my $prop (@output) { if ($prop =~ /^\s*svn:mime-type( : (\S+))?/) { $mime_type = $2; # 1.7.8 (r1416637) onwards changed the format of svnloop proplist --verbose # from propname : propvalue format, to values in an indent list on following lines if (not $mime_type) { if ($output_line + 1 >= scalar(@output)) { die "$0: Unexpected EOF reading proplist.\n"; } my $next_line_pval_indented = $output[$output_line + 1]; if ($next_line_pval_indented =~ /^\s{4}(.*)/) { $mime_type = $1; } } } elsif ($prop =~ /^\s*svn:eol-style( : (\S+))?/) { $eol_style = $2; if (not $eol_style) { if ($output_line + 1 >= scalar(@output)) { die "$0: Unexpected EOF reading proplist.\n"; } my $next_line_pval_indented = $output[$output_line + 1]; if ($next_line_pval_indented =~ /^\s{4}(.*)/) { $eol_style = $1; } } } $output_line++; } # Detect error conditions and add them to @errors if (not $mime_type) { push @errors, "$path : svn:mime-type is not set"; } elsif ($check_text_eol and $mime_type =~ /^text\// and not $eol_style) { push @errors, "$path : svn:mime-type=$mime_type but svn:eol-style is not set"; } } # If there are any errors list the problem files and give information # on how to avoid the problem. Hopefully people will set up auto-props # and will not see this verbose message more than once. if (@errors) { my $addition1 = ''; my $addition2 = ''; my $addition3 = ''; if ($check_prop_modified_files) { $addition1 = '/property-modified'; } if ($check_text_eol) { $addition2 = " In addition text files must have the svn:eol-style property set.\n"; $addition3 = " svn propset svn:eol-style native path/of/file\n"; } warn "$0:\n\n", join("\n", @errors), "\n\n", <<"EOS"; Every added$addition1 file must have the svn:mime-type property set. $addition2 For binary files try running svn propset svn:mime-type application/octet-stream path/of/file For text files try svn propset svn:mime-type text/plain path/of/file $addition3 You may want to consider uncommenting the auto-props section in your ~/.subversion/config file. Read the Subversion book (https://svnbook.red-bean.com/), Chapter 7, Properties section, Automatic Property Setting subsection for more help. EOS exit 1; } else { exit 0; } sub usage { warn "@_\n" if @_; die "usage: $0 REPOS TXN-NAME\n"; } sub safe_read_from_pipe { unless (@_) { croak "$0: safe_read_from_pipe passed no arguments.\n"; } print "Running @_\n"; my $pid = open(SAFE_READ, '-|', @_); unless (defined $pid) { die "$0: cannot fork: $!\n"; } unless ($pid) { open(STDERR, ">&STDOUT") or die "$0: cannot dup STDOUT: $!\n"; exec(@_) or die "$0: cannot exec `@_': $!\n"; } my @output; while () { chomp; push(@output, $_); } close(SAFE_READ); my $result = $?; my $exit = $result >> 8; my $signal = $result & 127; my $cd = $result & 128 ? "with core dump" : ""; if ($signal or $cd) { warn "$0: pipe from `@_' failed $cd: exit=$exit signal=$signal\n"; } if (wantarray) { return ($result, @output); } else { return $result; } } sub read_from_process { unless (@_) { croak "$0: read_from_process passed no arguments.\n"; } my ($status, @output) = &safe_read_from_pipe(@_); if ($status) { if (@output) { die "$0: `@_' failed with this output:\n", join("\n", @output), "\n"; } else { die "$0: `@_' failed with no output.\n"; } } else { return @output; } }