Subversion Autoprops

I have noticed in particular with Subversion that in an environment with different operating systems, you often end up with mixed line endings in files unless some measures are taken to combat this. This is a particular issue with using an svn master with git mirrors as you may see problems with git-svn.

Subversion allows metadata to be stored and versioned against any file or directory in the repository using properties. Subversion has its own namespace for properties that extend its capabilities to manage the items under source control. Some of the key properties are:

  • svn:mime-type - value is returned as the Content-type HTTP header on GET requests (useful when browsing the repository)
  • svn:keywords - provides keyword expansion such as Author, Date, Id and Revision, and ensuring that changes to these values do not cause differences
  • svn:eol-style - handles CR+LF issues in heterogeneous operating system environments
  • svn:executable - will set execution mask correctly on operating systems that support it

A key part of this is ensuring that properties are set effectively. Subversion provides a mechanism called autoprops to set default properties on files and directory based on pattern matching the filename. Autoprops does not modify existing files as you may need to deviate from the defaults in rare cases, so addition is chosen as a suitable point to consider what these values should be.

Configuring autoprops

All subversion clients support autoprops and this is usually configured in the runtime configuration area. This file is usually located at ~/.subversion/config although this may vary depending on the subversion client and the operating system.

Modify the subversion configuration file (config), making the following changes:

  1. Ensure enable-auto-props = yes is present and not commented out
  2. Ensure the [auto-props] section at the end of the file contains at least the following entries:
*.bat = svn:mime-type=text/plain;svn:eol-style=native;svn:executable=on
*.c = svn:mime-type=text/plain;svn:eol-style=native
*.cmd = svn:mime-type=text/plain;svn:eol-style=native;svn:executable=on
*.cpp = svn:mime-type=text/plain;svn:eol-style=native
*.css = svn:mime-type=text/css;svn:eol-style=native
*.doc = svn:mime-type=application/msword
*.dtd = svn:mime-type=text/plain;svn:eol-style=native
*.erb = svn:mime-type=text/plain;svn:eol-style=native
*.gif = svn:mime-type=image/gif
*.h = svn:mime-type=text/plain;svn:keywords=Author Date Id Revision;svn:eol-style=native
*.html = svn:mime-type=text/html;svn:eol-style=native
*.java = svn:mime-type=text/plain;svn:eol-style=native;svn:keywords=Author Date Id Revision
*.jpg = svn:mime-type=image/jpeg
*.js = svn:mime-type=text/plain;svn:eol-style=native
*.jsp = svn:mime-type=text/plain;svn:eol-style=native
*.png = svn:mime-type=image/png
*.properties = svn:mime-type=text/plain;svn:eol-style=native
*.rb = svn:mime-type=text/plain;svn:eol-style=native;svn:executable=on
*.sh = svn:mime-type=text/plain;svn:eol-style=native;svn:executable=on
*.sql = svn:mime-type=text/plain;svn:eol-style=native
*.tld = svn:mime-type=text/xml;svn:eol-style=native
*.txt = svn:mime-type=text/plain;svn:eol-style=native
*.xls = svn:mime-type=application/msexcel
*.xml = svn:mime-type=text/xml;svn:eol-style=native
*.xsd = svn:mime-type=text/xml;svn:eol-style=native
*.xsl = svn:mime-type=text/xml;svn:eol-style=native
*.yml = svn:mime-type=text/plain;svn:eol-style=native
.checkstyle = svn:mime-type=text/plain;svn:eol-style=native
.classpath = svn:mime-type=text/plain;svn:eol-style=native
.project = svn:mime-type=text/plain;svn:eol-style=native
Makefile = svn:eol-style=native

The above list is not definitive, so add any file types common to your development process.

Fixing existing autoprops

The following script is designed to run in a bash shell and requires ruby. It reads the subversion config file from ~/.subversion/config and applies the rules to files already added to subversion in the current directory and its children - effectively applying autoprops to the file as if it were a new addition.

In particular, note that:

  • the modification will be to the working copy and will therefore need to be committed as required
  • the use of dos2unix and unix2dos gives EOL consistency. Subversion may complain if you try to change the eol-style on a file with inconsistent line endings.
  • this can generate a lot of changes and cause some major conflicts if people have uncommitted local modifications. It is advised to do this during a quiet period.
#!/usr/bin/env ruby
require 'optparse'
require 'fileutils'

options = { :dryrun => false }
opts = OptionParser.new do |opts|
  opts.banner = "fix_svn_autoprops"

  opts.on("-n", "--dry-run", "Show what would happen but don't actually do anything") do |tf|
    options[:dryrun] = tf
  end

  opts.on("--fix-eols-windows", "Fix eol for windows if eol-style property is set") do |tf|
    options[:fixeols] = :windows
  end

  opts.on("--fix-eols-linux", "Fix eol for linux (and mac) if eol-style property is set") do |tf|
    options[:fixeols] = :linux
  end

  opts.on_tail("-h", "--help", "Show this message") do
    puts opts
    exit
  end
end
opts.parse!

autoprops = Hash.new {|h,k| h[k] = {}}
File.open(File.expand_path("~/.subversion/config"), "r") do |file|
  section = nil
  while line = file.gets
    line.chomp!.strip!
    next if line =~ /^$/
    next if line =~ /^#.*$/
    if line =~ /^\[[\w-]*\]$/
      section = line
      next
    end
    next unless section == "[auto-props]"
    # parse auto props line
    m, filename_pattern, props = *line.match(/([^\s=]+)\s*=\s*(.+)$/)
    props.split(';').each do |prop|
      propname, value = prop.split('=')
      autoprops[filename_pattern][propname] = value
    end
  end
end

autoprops.each do |filename_pattern, props|
  puts "Fixing #{filename_pattern} with #{props.inspect}"
  command = %Q{find . -name .svn -prune -o -name target -prune -o -name test-output -prune -o -name "#{filename_pattern}" -print0}
  props.each do |propname, value|
    if propname == 'svn:eol-style'
      case options[:fixeols]
      when :windows
        command << %Q{ -exec dos2unix '{}' \\;}
        command << %Q{ -exec unix2dos '{}' \\;}
      when :linux
        command << %Q{ -exec unix2dos '{}' \\;}
        command << %Q{ -exec dos2unix '{}' \\;}
      end
    end
    command << %Q{ -exec svn ps "#{propname}" "#{value}" '{}' \\;}
  end
  puts command
  system command unless options[:dryrun]
end

Comments

blog comments powered by Disqus
Fork me on GitHub