summaryrefslogtreecommitdiff
path: root/tool/file2lastrev.rb
blob: 2965d2136f041b8a816aa205b91670852737818a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env ruby

ENV.delete('PWD')

require 'optparse'

unless File.respond_to? :realpath
  require 'pathname'
  def File.realpath(arg)
    Pathname(arg).realpath.to_s
  end
end

Program = $0

class VCS
  class NotFoundError < RuntimeError; end

  @@dirs = []
  def self.register(dir)
    @@dirs << [dir, self]
  end

  def self.detect(path)
    @@dirs.sort.reverse_each do |dir, klass|
      return klass.new(path) if File.directory?("#{path}/#{dir}")
    end
    raise VCS::NotFoundError, "does not seem to be under a vcs: #{path}"
  end

  def initialize(path)
    @srcdir = path
    super()
  end

  # return a pair of strings, the last revision and the last revision in which
  # +path+ was modified.
  def get_revisions(path)
    path = relative_to(path)
    last, changed, *rest = Dir.chdir(@srcdir) {self.class.get_revisions(path)}
    last or raise "last revision not found"
    changed or raise "changed revision not found"
    return last, changed, *rest
  end

  def relative_to(path)
    if path
      srcdir = File.realpath(@srcdir)
      path = File.realpath(path)
      list1 = srcdir.split(%r{/})
      list2 = path.split(%r{/})
      while !list1.empty? && !list2.empty? && list1.first == list2.first
        list1.shift
        list2.shift
      end
      if list1.empty? && list2.empty?
        "."
      else
        ([".."] * list1.length + list2).join("/")
      end
    else
      '.'
    end
  end

  class SVN < self
    register(".svn")

    def self.get_revisions(path)
      begin
        nulldevice = %w[/dev/null NUL NIL: NL:].find {|dev| File.exist?(dev)}
        if nulldevice
          save_stderr = STDERR.dup
          STDERR.reopen nulldevice, 'w'
        end
        info_xml = `svn info --xml "#{path}"`
      ensure
        if save_stderr
          STDERR.reopen save_stderr
          save_stderr.close
        end
      end
      _, last, _, changed, _ = info_xml.split(/revision="(\d+)"/)
      [last, changed]
    end
  end

  class GIT < self
    register(".git")

    def self.get_revisions(path)
      logcmd = %Q[git log -n1 --grep="^ *git-svn-id: .*@[0-9][0-9]* "]
      idpat = /git-svn-id: .*?@(\d+) \S+\Z/
      last = `#{logcmd}`[idpat, 1]
      changed = path ? `#{logcmd} "#{path}"`[idpat, 1] : last
      [last, changed]
    end
  end
end

@output = nil
def self.output=(output)
  if @output and @output != output
    raise "you can specify only one of --changed, --revision.h and --doxygen"
  end
  @output = output
end
@suppress_not_found = false

srcdir = nil
parser = OptionParser.new {|opts|
  opts.on("--srcdir=PATH", "use PATH as source directory") do |path|
    srcdir = path
  end
  opts.on("--changed", "changed rev") do
    self.output = :changed
  end
  opts.on("--revision.h", "RUBY_REVISION macro") do
    self.output = :revision_h
  end
  opts.on("--doxygen", "Doxygen format") do
    self.output = :doxygen
  end
  opts.on("-q", "--suppress_not_found") do
    @suppress_not_found = true
  end
}
parser.parse! rescue abort "#{File.basename(Program)}: #{$!}\n#{parser}"

srcdir = srcdir ? srcdir : File.dirname(File.dirname(Program))
begin
  vcs = VCS.detect(srcdir)
rescue VCS::NotFoundError => e
  abort "#{File.basename(Program)}: #{e.message}" unless @suppress_not_found
else
  begin
    last, changed = vcs.get_revisions(ARGV.shift)
  rescue => e
    abort "#{File.basename(Program)}: #{e.message}" unless @suppress_not_found
    exit false
  end
end

case @output
when :changed, nil
  puts changed
when :revision_h
  puts "#define RUBY_REVISION #{changed.to_i}"
when :doxygen
  puts "r#{changed}/r#{last}"
else
  raise "unknown output format `#{@output}'"
end