whistle 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/History.txt +3 -0
  2. data/README.txt +38 -0
  3. data/bin/whistle +90 -0
  4. data/lib/config.rb +19 -0
  5. data/lib/phash.rb +16 -0
  6. data/lib/relay.rb +24 -0
  7. data/lib/resource.rb +113 -0
  8. data/lib/ssl_patch.rb +15 -0
  9. data/lib/switchbox.rb +54 -0
  10. data/lib/time_ext.rb +30 -0
  11. data/lib/version.rb +3 -0
  12. data/sample/config.yml +12 -0
  13. data/vendor/rscm-0.5.1-patched-stripped/README +218 -0
  14. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm.rb +14 -0
  15. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/abstract_log_parser.rb +35 -0
  16. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/base.rb +289 -0
  17. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/command_line.rb +146 -0
  18. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/difftool.rb +44 -0
  19. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/line_editor.rb +46 -0
  20. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/mockit.rb +157 -0
  21. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/parser.rb +39 -0
  22. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/path_converter.rb +60 -0
  23. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/platform.rb +26 -0
  24. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision.rb +103 -0
  25. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision_file.rb +85 -0
  26. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision_poller.rb +93 -0
  27. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revisions.rb +79 -0
  28. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/clearcase.rb +182 -0
  29. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/cvs.rb +374 -0
  30. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/cvs_log_parser.rb +154 -0
  31. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/darcs.rb +120 -0
  32. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/darcs_log_parser.rb +65 -0
  33. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/monotone.rb +338 -0
  34. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/monotone_log_parser.rb +109 -0
  35. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/mooky.rb +6 -0
  36. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/perforce.rb +216 -0
  37. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/star_team.rb +104 -0
  38. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/subversion.rb +397 -0
  39. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/subversion_log_parser.rb +165 -0
  40. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/tempdir.rb +17 -0
  41. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/time_ext.rb +11 -0
  42. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/version.rb +13 -0
  43. data/vendor/ruby-feedparser-0.5-stripped/README +14 -0
  44. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser.rb +28 -0
  45. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/feedparser.rb +300 -0
  46. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/filesizes.rb +12 -0
  47. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/html-output.rb +126 -0
  48. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/html2text-parser.rb +409 -0
  49. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/rexml_patch.rb +28 -0
  50. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/sgml-parser.rb +332 -0
  51. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/text-output.rb +83 -0
  52. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/textconverters.rb +120 -0
  53. metadata +132 -0
@@ -0,0 +1,154 @@
1
+ require 'rscm/revision'
2
+ require 'rscm/abstract_log_parser'
3
+ require 'ftools'
4
+ require 'time'
5
+
6
+ module RSCM
7
+
8
+ class CvsLogParser < AbstractLogParser
9
+ REVISION_SEPARATOR = /^----------------------------$/ unless defined? REVISION_SEPARATOR
10
+ ENTRY_SEPARATOR = /^=============================================================================$/ unless defined? ENTRY_SEPARATOR
11
+
12
+ attr_accessor :cvspath
13
+ attr_accessor :cvsmodule
14
+
15
+ def initialize(io)
16
+ super(io)
17
+ end
18
+
19
+ def parse_revisions
20
+ revisions = Revisions.new
21
+ while(log_entry = next_log_entry)
22
+ begin
23
+ parse_files(log_entry, revisions)
24
+ rescue Exception => e
25
+ $stderr.puts("could not parse log entry: #{log_entry}\ndue to: #{e.message}\n\t")
26
+ $stderr.puts(e.backtrace.join("\n\t"))
27
+ end
28
+ end
29
+ revisions.sort!
30
+ revisions
31
+ end
32
+
33
+ def next_log_entry
34
+ read_until_matching_line(ENTRY_SEPARATOR)
35
+ end
36
+
37
+ def split_entries(log_entry)
38
+ entries = [""]
39
+ log_entry.each_line do |line|
40
+ if line=~REVISION_SEPARATOR
41
+ entries << ""
42
+ else
43
+ entries[entries.length-1] << line
44
+ end
45
+ end
46
+ entries
47
+ end
48
+
49
+ def parse_files(log_entry, revisions)
50
+ entries = split_entries(log_entry)
51
+
52
+ entries[1..entries.length].each do |entry|
53
+ file = parse_file(entry)
54
+ next if file.nil?
55
+ file.path = parse_path(entries[0])
56
+ file.status = RevisionFile::ADDED if file.native_revision_identifier =~ /1\.1$/
57
+ revisions.add(file)
58
+ end
59
+ nil
60
+ end
61
+
62
+ def parse_head_revision(first_entry)
63
+ head_revision = extract_match(first_entry, /^head: (.*?)$/m)
64
+ end
65
+
66
+ def parse_path(first_entry)
67
+ working_file = extract_match(first_entry, /^Working file: (.*?)$/m)
68
+ return convert_all_slashes_to_forward_slashes(working_file) unless working_file.nil? || working_file == ""
69
+ make_relative_to_module(extract_required_match(first_entry, /^RCS file: (.*?)(,v|$)/m))
70
+ end
71
+
72
+ def make_relative_to_module(file)
73
+ return file if cvspath.nil? || cvsmodule.nil? || file.nil?
74
+ cvspath = convert_all_slashes_to_forward_slashes(self.cvspath)
75
+ convert_all_slashes_to_forward_slashes(file).gsub(/^#{cvspath}\/#{cvsmodule}\//, "")
76
+ end
77
+
78
+ def parse_file(file_entry)
79
+ raise "can't parse: #{file_entry}" if file_entry =~ REVISION_SEPARATOR
80
+
81
+ file_entry_lines = file_entry.split(/\r?\n/)
82
+
83
+ file = RevisionFile.new
84
+ file.native_revision_identifier = extract_match(file_entry_lines[0], /revision (.*)$/)
85
+ file.previous_native_revision_identifier = determine_previous_native_revision_identifier(file.native_revision_identifier)
86
+
87
+ time = extract_required_match(file_entry_lines[1], /date: (.*?)(;|$)/)
88
+ if(time.strip.length == 19)
89
+ # CVS 1.11.x doesn't specify timezone (but assumes UTC), so we'll add it here.
90
+ time += " +0000"
91
+ end
92
+ file.time = Time.parse(time).utc
93
+ file.developer = extract_match(file_entry_lines[1], /author: (.*?);/)
94
+
95
+ state = extract_match(file_entry_lines[1], /state: (.*?);/)
96
+ file.status = STATES[state]
97
+
98
+ message_start = 2
99
+ branches = nil
100
+ if(file_entry_lines[2] =~ /^branches:\s+(.*);/)
101
+ message_start = 3
102
+ branches = $1
103
+ end
104
+
105
+ file.message = file_entry_lines[message_start..-1].join("\n")
106
+
107
+ # Ignore the initial revision from import - we will have two of them
108
+ if(file.message == "Initial revision" && branches == "1.1.1")
109
+ return nil
110
+ end
111
+
112
+ file
113
+ end
114
+
115
+ def determine_previous_native_revision_identifier(revision)
116
+ if revision =~ /(.*)\.(.*)/
117
+ big_version_number = $1
118
+ small_version_number = $2.to_i
119
+ if small_version_number == 1
120
+ nil
121
+ else
122
+ "#{big_version_number}.#{small_version_number - 1}"
123
+ end
124
+ else
125
+ nil
126
+ end
127
+ end
128
+
129
+ def extract_required_match(string, regexp)
130
+ if(string =~ regexp)
131
+ return($1)
132
+ else
133
+ $stderr.puts("can't parse: '#{string}'\nexpected to match regexp: #{regexp.to_s}")
134
+ end
135
+ end
136
+
137
+ def extract_match(string, regexp)
138
+ if string=~regexp
139
+ return($1)
140
+ else
141
+ ""
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ # The state field is "Exp" both for added and modified files. retards!
148
+ # We need some additional logic to figure out whether it is added or not.
149
+ # Maybe look at the revision. (1.1 means new I think. - deal with it later)
150
+ STATES = {"dead" => RevisionFile::DELETED, "Exp" => RevisionFile::MODIFIED} unless defined? STATES
151
+
152
+ end
153
+
154
+ end
@@ -0,0 +1,120 @@
1
+ require 'tempfile'
2
+ require 'fileutils'
3
+ require 'rscm'
4
+
5
+ module RSCM
6
+ class Darcs < Base
7
+ # See http://progetti.arstecnica.it/tailor/browser/vcpx/darcs.py
8
+ attr_accessor :dir
9
+
10
+ def installed?
11
+ begin
12
+ darcs("--version", {})
13
+ true
14
+ rescue
15
+ false
16
+ end
17
+ end
18
+
19
+ def initialize(dir=".")
20
+ @dir = File.expand_path(dir)
21
+ end
22
+
23
+ def can_create_central?
24
+ true
25
+ end
26
+
27
+ def create_central
28
+ with_working_dir(@dir) do
29
+ darcs("initialize")
30
+ end
31
+ end
32
+
33
+ def import_central(dir, message)
34
+ ENV["EMAIL"] = "dcontrol@codehaus.org"
35
+ FileUtils.cp_r(Dir.glob("#{dir}/*"), @dir)
36
+ with_working_dir(@dir) do
37
+ darcs("add --recursive .")
38
+
39
+ logfile = Tempfile.new("darcs_logfile")
40
+ logfile.print("something nice\n")
41
+ logfile.print(message + "\n")
42
+ logfile.close
43
+
44
+ darcs("record --all --logfile #{PathConverter.filepath_to_nativepath(logfile.path, false)}")
45
+ end
46
+ end
47
+
48
+ def commit(message)
49
+ logfile = Tempfile.new("darcs_logfile")
50
+ logfile.print("something nice\n")
51
+ logfile.print(message + "\n")
52
+ logfile.close
53
+
54
+ with_working_dir(@checkout_dir) do
55
+ darcs("record --all --logfile #{PathConverter.filepath_to_nativepath(logfile.path, false)}")
56
+ end
57
+ end
58
+
59
+ def add(relative_filename)
60
+ with_working_dir(@checkout_dir) do
61
+ darcs("add #{relative_filename}")
62
+ end
63
+ end
64
+
65
+ def checked_out?
66
+ File.exists?("#{@checkout_dir}/_darcs")
67
+ end
68
+
69
+ def uptodate?(from_identifier)
70
+ if (!checked_out?(@checkout_dir))
71
+ false
72
+ else
73
+ with_working_dir(@checkout_dir) do
74
+ darcs("pull --dry-run #{@dir}") do |io|
75
+ io.each_line do |line|
76
+ if (line =~ /No remote changes to pull in!/)
77
+ true
78
+ else
79
+ false
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def revisions(from_identifier, to_identifier=Time.infinity)
88
+ from_identifier = Time.epoch if from_identifier.nil?
89
+ to_identifier = Time.infinity if to_identifier.nil?
90
+ with_working_dir(@checkout_dir) do
91
+ darcs("changes --summary --xml-output") do |stdout|
92
+ DarcsLogParser.new.parse_revisions(stdout, from_identifier, to_identifier)
93
+ end
94
+ end
95
+ end
96
+
97
+ def supports_trigger?
98
+ true
99
+ end
100
+
101
+ protected
102
+
103
+ def checkout_silent(to_identifier) # :yield: file
104
+ with_working_dir(File.dirname(checkout_dir)) do
105
+ darcs("get --repo-name #{File.basename(checkout_dir)} #{@dir}")
106
+ end
107
+ end
108
+
109
+ def ignore_paths
110
+ return [/_darcs/]
111
+ end
112
+
113
+ private
114
+
115
+ def darcs(darcs_cmd, options={}, &proc)
116
+ cmd = "darcs #{darcs_cmd}"
117
+ execute(cmd, options, &proc)
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,65 @@
1
+ require 'rscm'
2
+ require 'time'
3
+ require 'stringio'
4
+ require 'rexml/document'
5
+
6
+ module RSCM
7
+ class DarcsLogParser
8
+ def parse_revisions(io, from_identifier=Time.epoch, to_identifier=Time.infinity)
9
+ revisions = Revisions.new
10
+
11
+ doc = REXML::Document.new(io)
12
+
13
+ path_revisions = {}
14
+ doc.elements.each("//patch") do |element|
15
+ revision = parse_revision(element.to_s, path_revisions)
16
+ if ((from_identifier <= revision.time) && (revision.time <= to_identifier))
17
+ revisions.add(revision)
18
+ end
19
+ end
20
+
21
+ revisions.each do |revision|
22
+ revision.each do |change|
23
+ current_index = path_revisions[change.path].index(change.native_revision_identifier)
24
+ change.previous_native_revision_identifier = path_revisions[change.path][current_index + 1]
25
+ end
26
+ end
27
+
28
+ revisions
29
+ end
30
+
31
+ def parse_revision(revision_io, path_revisions)
32
+ revision = Revision.new
33
+
34
+ doc = REXML::Document.new(revision_io)
35
+
36
+ doc.elements.each("patch") do |element|
37
+ revision.identifier = element.attributes['hash']
38
+ revision.developer = element.attributes['author']
39
+ revision.time = Time.parse(element.attributes['local_date'])
40
+ revision.message = element.elements["comment"].text
41
+ revision.message.lstrip!
42
+ revision.message.rstrip!
43
+
44
+ element.elements["summary"].elements.each("add_file") do |file|
45
+ add_changes(revision, file.text.strip, RevisionFile::ADDED, path_revisions)
46
+ end
47
+ element.elements["summary"].elements.each("modify_file") do |file|
48
+ add_changes(revision, file.text.strip, RevisionFile::MODIFIED, path_revisions)
49
+ end
50
+ end
51
+
52
+ revision
53
+ end
54
+
55
+ private
56
+
57
+ def add_changes(revision, path, state, path_revisions)
58
+ revision << RevisionFile.new(path, state, revision.developer, nil, revision.identifier, revision.time)
59
+
60
+ path_revisions[path] ||= []
61
+ path_revisions[path] << revision.identifier
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,338 @@
1
+ require 'rscm/line_editor'
2
+ require 'fileutils'
3
+ require 'rscm'
4
+
5
+ module RSCM
6
+ class Monotone < Base
7
+ attr_accessor :db_file
8
+ attr_accessor :branch
9
+ attr_accessor :key
10
+ attr_accessor :passphrase
11
+ attr_accessor :keys_file
12
+ attr_accessor :server
13
+
14
+ def initialize(branch=nil, key=nil, passphrase=nil, keys_file=nil, server=nil, central_checkout_dir=nil)
15
+ @branch = branch
16
+ @key = key
17
+ @passphrase = passphrase
18
+ @keys_file = keys_file
19
+ @server = server
20
+ @central_checkout_dir = File.expand_path(central_checkout_dir) unless central_checkout_dir.nil?
21
+ end
22
+
23
+ def add(relative_filename)
24
+ db = db(@checkout_dir)
25
+ with_working_dir(@checkout_dir) do
26
+ monotone("add #{relative_filename}", db)
27
+ end
28
+ end
29
+
30
+ def move(relative_src, relative_dest)
31
+ with_working_dir(@checkout_dir) do
32
+ monotone("rename #{relative_src} #{relative_dest}", db)
33
+ FileUtils.mv(relative_src, relative_dest)
34
+ end
35
+ end
36
+
37
+ def can_create_central?
38
+ @server == "localhost" && !@central_checkout_dir.nil?
39
+ end
40
+
41
+ def central_exists?
42
+ @central_checkout_dir && @serve_pid
43
+ end
44
+
45
+ def create_central
46
+ init(@central_checkout_dir)
47
+ # create empty working copy
48
+ dir = PathConverter.filepath_to_nativepath(@central_checkout_dir, false)
49
+ # set up a working copy
50
+ monotone("setup #{dir}")
51
+ start_serve
52
+ end
53
+
54
+ def start_serve
55
+ mode = File::CREAT|File::WRONLY
56
+ if File.exist?(rcfile)
57
+ mode = File::APPEND|File::WRONLY
58
+ end
59
+
60
+ begin
61
+ File.open(rcfile, mode) do |file|
62
+ file.puts("function get_netsync_anonymous_read_permitted(collection)")
63
+ file.puts(" return true")
64
+ file.puts("end")
65
+ end
66
+ rescue => e
67
+ puts e.message
68
+ puts e.backtrace.join("\n")
69
+ raise "Didn't have permission to write to #{rcfile}."
70
+ end
71
+
72
+ @serve_pid = fork do
73
+ #Signal.trap("HUP") { puts "Monotone server shutting down..."; exit }
74
+ monotone("serve --rcfile=\"#{rcfile}\" #{@server} #{@branch}", db(@central_checkout_dir)) do |io|
75
+ puts "PASSPHRASE: #{@passphrase}"
76
+ io.puts(@passphrase)
77
+ io.close_write
78
+ end
79
+ end
80
+ Process.detach(@serve_pid)
81
+ end
82
+
83
+ def stop_serve
84
+ Process.kill("HUP", @serve_pid) if @serve_pid
85
+ Process.waitpid2(@serve_pid) if @serve_pid
86
+ @serve_pid = nil
87
+ end
88
+
89
+ def destroy_central
90
+ stop_serve
91
+ FileUtils.rm_rf(@central_checkout_dir) if File.exist?(@central_checkout_dir)
92
+ FileUtils.rm(db(@central_checkout_dir)) if File.exist?(db(@central_checkout_dir))
93
+ puts "Destroyed Monotone server"
94
+ end
95
+
96
+ def transactional?
97
+ true
98
+ end
99
+
100
+ def import_central(dir, message)
101
+ FileUtils.cp_r(Dir["#{dir}/*"], @central_checkout_dir)
102
+ with_working_dir(@central_checkout_dir) do
103
+ monotone("add .")
104
+ commit_in_dir(message, @central_checkout_dir)
105
+ end
106
+ end
107
+
108
+ def checked_out?
109
+ mt = File.expand_path("#{@checkout_dir}/MT")
110
+ File.exists?(mt)
111
+ end
112
+
113
+ def uptodate?(identifier=nil)
114
+ if (!checked_out?)
115
+ false
116
+ else
117
+ pull
118
+
119
+ rev = identifier ? identifier : head_revision
120
+ local_revision == rev
121
+ end
122
+ end
123
+
124
+ def revisions(from_identifier, to_identifier=Time.infinity)
125
+ checkout(to_identifier)
126
+ to_identifier = Time.infinity if to_identifier.nil?
127
+ with_working_dir(checkout_dir) do
128
+ monotone("log") do |stdout|
129
+ MonotoneLogParser.new.parse_revisions(stdout, from_identifier, to_identifier)
130
+ end
131
+ end
132
+ end
133
+
134
+ def commit(message)
135
+ commit_in_dir(message, @checkout_dir)
136
+ with_working_dir(@checkout_dir) do
137
+ monotone("push #{@server} #{@branch}") do |io|
138
+ io.puts(@passphrase)
139
+ io.close_write
140
+ io.read
141
+ end
142
+ end
143
+ end
144
+
145
+ def supports_trigger?
146
+ true
147
+ end
148
+
149
+ def trigger_mechanism
150
+ "MT/monotonerc"
151
+ end
152
+
153
+ # http://www.venge.net/monotone/monotone.html#Hook-Reference
154
+ def install_trigger(trigger_command, install_dir)
155
+ stop_serve
156
+ if (WINDOWS)
157
+ install_win_trigger(trigger_comand, install_dir)
158
+ else
159
+ install_unix_trigger(trigger_command, install_dir)
160
+ end
161
+ start_serve
162
+ end
163
+
164
+ def trigger_installed?(trigger_command, install_dir)
165
+ File.exist?(rcfile)
166
+ end
167
+
168
+ def uninstall_trigger(trigger_command, install_dir)
169
+ stop_serve
170
+ File.delete(rcfile)
171
+ start_serve
172
+ end
173
+
174
+ def diff(change, &block)
175
+ checkout(change.revision)
176
+ with_working_dir(@checkout_dir) do
177
+ monotone("diff --revision=#{change.previous_native_revision_identifier} #{change.path}") do |stdout|
178
+ yield stdout
179
+ end
180
+ end
181
+ end
182
+
183
+ protected
184
+
185
+ # Checks out silently. Called by superclass' checkout.
186
+ def checkout_silent(to_identifier)
187
+ # raise "Monotone doesn't support checkout to time. Please use identifiers instead." if to_identifier.is_a?(Time)
188
+ db_file = db(@checkout_dir)
189
+ if(!File.exist?(db_file))
190
+ init(@checkout_dir)
191
+ end
192
+
193
+ pull
194
+ checked_out = checked_out?
195
+
196
+ with_working_dir(@checkout_dir) do
197
+ monotone("checkout .", db_file, @branch) unless checked_out
198
+
199
+ selector = expand_selector(to_identifier)
200
+ monotone("update #{selector}", db_file)
201
+ end
202
+ end
203
+
204
+ # Administrative files that should be ignored when counting files.
205
+ def ignore_paths
206
+ [/MT/, /\.mt-attrs/]
207
+ end
208
+
209
+ private
210
+
211
+ def commit_in_dir(message, dir)
212
+ db_file = db(dir)
213
+ with_working_dir(dir) do
214
+ monotone("commit --message='#{message}'", db_file, @branch, @key) do |io|
215
+ io.puts(@passphrase)
216
+ io.close_write
217
+ io.read
218
+ end
219
+ end
220
+ end
221
+
222
+ def pull
223
+ db_file = db(@checkout_dir)
224
+ with_working_dir(@checkout_dir) do
225
+ # pull from the "central" server
226
+ if(@server)
227
+ monotone("pull #{@server} #{@branch}", db_file) do |io|
228
+ io.puts(@passphrase)
229
+ io.close_write
230
+ io.read
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ def db(checkout_dir)
237
+ PathConverter.filepath_to_nativepath(checkout_dir + ".db", false)
238
+ end
239
+
240
+ # Initialises a monotone database
241
+ #
242
+ def init(dir)
243
+ dir = File.expand_path(dir)
244
+ db_file = db(dir)
245
+ raise "Database #{db_file} already exists" if File.exist?(db_file)
246
+ FileUtils.mkdir_p(File.dirname(db_file))
247
+ # create database
248
+ monotone("db init", db_file)
249
+ # TODO: do a genkey
250
+ monotone("read", db_file) do |io|
251
+ io.write(File.open(@keys_file).read)
252
+ io.close_write
253
+ end
254
+ end
255
+
256
+ def install_unix_trigger(trigger_command, install_dir)
257
+ mode = File::CREAT|File::WRONLY
258
+ if File.exist?(rcfile)
259
+ mode = File::APPEND|File::WRONLY
260
+ end
261
+
262
+ begin
263
+ File.open(rcfile, mode) do |file|
264
+ file.puts("function note_commit(new_id, certs)")
265
+ execstr = "\"" + trigger_command.split.join("\",\"") + "\""
266
+ file.puts(" execute(#{execstr})")
267
+ file.puts("end")
268
+ end
269
+ rescue => e
270
+ puts e.message
271
+ puts e.backtrace.join("\n")
272
+ raise "Didn't have permission to write to #{rcfile}."
273
+ end
274
+
275
+ # push to the "central" server
276
+ # monotone("push #{@server} #{@branch}", db(@central_checkout_dir))
277
+ end
278
+
279
+ def rcfile
280
+ "#{@central_checkout_dir}/MT/monotonerc"
281
+ end
282
+
283
+ def local_revision
284
+ local_revision = nil
285
+ rev_file = File.expand_path("#{checkout_dir}/MT/revision")
286
+ local_revision = File.open(rev_file).read.strip
287
+ local_revision
288
+ end
289
+
290
+ def head_revision
291
+ # FIXME: this will grab last head if heads are not merged.
292
+ head_revision = nil
293
+ monotone("heads", db(@checkout_dir), @branch) do |stdout|
294
+ stdout.each_line do |line|
295
+ next if (line =~ /^monotone:/)
296
+ head_revision = line.split(" ")[0]
297
+ end
298
+ end
299
+ head_revision
300
+ end
301
+
302
+ # See http://www.venge.net/monotone/monotone.html#Selectors
303
+ # Also see docs for expand_selector in the same document
304
+ # Dates are formatted with strftime-style %F, which is of style 2005-28-02,
305
+ # which is very coarse grained. Date identifiers are therefore discouraged.
306
+ def expand_selector(identifier)
307
+ if(identifier.is_a?(Time))
308
+ # Won't work:
309
+ # "d:#{identifier.strftime('%Y-%m-%d')}"
310
+ ""
311
+ else
312
+ "i:#{identifier}"
313
+ end
314
+ end
315
+
316
+ def monotone(monotone_cmd, db_file=nil, branch=nil, key=nil)
317
+ db_opt = db_file ? "--db=\"#{db_file}\"" : ""
318
+ branch_opt = branch ? "--branch=\"#{branch}\"" : ""
319
+ key_opt = key ? "--key=\"#{key}\"" : ""
320
+ rcfile_opt = @rcfile ? "--rcfile=\"#{@rcfile}\"" : ""
321
+ cmd = "monotone #{db_opt} #{branch_opt} #{key_opt} #{rcfile_opt} #{monotone_cmd}"
322
+ Better.popen(cmd, "r+") do |io|
323
+ if(block_given?)
324
+ return(yield(io))
325
+ else
326
+ # just read stdout so we can exit
327
+ io.read
328
+ end
329
+ end
330
+ end
331
+
332
+ def monotone_date(time)
333
+ return nil unless time
334
+ time.utc.strftime("%Y-%m-%dT%H:%M:%S")
335
+ end
336
+
337
+ end
338
+ end