1# vcs 2 3require 'time' 4 5ENV.delete('PWD') 6 7unless File.respond_to? :realpath 8 require 'pathname' 9 def File.realpath(arg) 10 Pathname(arg).realpath.to_s 11 end 12end 13 14class VCS 15 class NotFoundError < RuntimeError; end 16 17 @@dirs = [] 18 def self.register(dir) 19 @@dirs << [dir, self] 20 end 21 22 def self.detect(path) 23 @@dirs.each do |dir, klass| 24 return klass.new(path) if File.directory?(File.join(path, dir)) 25 prev = path 26 loop { 27 curr = File.realpath(File.join(prev, '..')) 28 break if curr == prev # stop at the root directory 29 return klass.new(path) if File.directory?(File.join(curr, dir)) 30 prev = curr 31 } 32 end 33 raise VCS::NotFoundError, "does not seem to be under a vcs: #{path}" 34 end 35 36 def initialize(path) 37 @srcdir = path 38 super() 39 end 40 41 # return a pair of strings, the last revision and the last revision in which 42 # +path+ was modified. 43 def get_revisions(path) 44 path = relative_to(path) 45 last, changed, modified, *rest = Dir.chdir(@srcdir) {self.class.get_revisions(path)} 46 last or raise "last revision not found" 47 changed or raise "changed revision not found" 48 modified &&= Time.parse(modified) 49 return last, changed, modified, *rest 50 end 51 52 def relative_to(path) 53 if path 54 srcdir = File.realpath(@srcdir) 55 path = File.realpath(path) 56 list1 = srcdir.split(%r{/}) 57 list2 = path.split(%r{/}) 58 while !list1.empty? && !list2.empty? && list1.first == list2.first 59 list1.shift 60 list2.shift 61 end 62 if list1.empty? && list2.empty? 63 "." 64 else 65 ([".."] * list1.length + list2).join("/") 66 end 67 else 68 '.' 69 end 70 end 71 72 class SVN < self 73 register(".svn") 74 75 def self.get_revisions(path) 76 begin 77 nulldevice = %w[/dev/null NUL NIL: NL:].find {|dev| File.exist?(dev)} 78 if nulldevice 79 save_stderr = STDERR.dup 80 STDERR.reopen nulldevice, 'w' 81 end 82 info_xml = `svn info --xml "#{path}"` 83 ensure 84 if save_stderr 85 STDERR.reopen save_stderr 86 save_stderr.close 87 end 88 end 89 _, last, _, changed, _ = info_xml.split(/revision="(\d+)"/) 90 modified = info_xml[/<date>([^<>]*)/, 1] 91 [last, changed, modified] 92 end 93 end 94 95 class GIT < self 96 register(".git") 97 98 def self.get_revisions(path) 99 logcmd = %Q[git log -n1 --grep="^ *git-svn-id: .*@[0-9][0-9]* "] 100 idpat = /git-svn-id: .*?@(\d+) \S+\Z/ 101 last = `#{logcmd}`[idpat, 1] 102 if path 103 log = `#{logcmd} "#{path}"` 104 changed = log[idpat, 1] 105 modified = `git log --format=%ai -- #{path}` 106 else 107 changed = last 108 end 109 [last, changed, modified] 110 end 111 end 112end 113