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.
- data/History.txt +3 -0
- data/README.txt +38 -0
- data/bin/whistle +90 -0
- data/lib/config.rb +19 -0
- data/lib/phash.rb +16 -0
- data/lib/relay.rb +24 -0
- data/lib/resource.rb +113 -0
- data/lib/ssl_patch.rb +15 -0
- data/lib/switchbox.rb +54 -0
- data/lib/time_ext.rb +30 -0
- data/lib/version.rb +3 -0
- data/sample/config.yml +12 -0
- data/vendor/rscm-0.5.1-patched-stripped/README +218 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm.rb +14 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/abstract_log_parser.rb +35 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/base.rb +289 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/command_line.rb +146 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/difftool.rb +44 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/line_editor.rb +46 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/mockit.rb +157 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/parser.rb +39 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/path_converter.rb +60 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/platform.rb +26 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision.rb +103 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision_file.rb +85 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision_poller.rb +93 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revisions.rb +79 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/clearcase.rb +182 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/cvs.rb +374 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/cvs_log_parser.rb +154 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/darcs.rb +120 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/darcs_log_parser.rb +65 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/monotone.rb +338 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/monotone_log_parser.rb +109 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/mooky.rb +6 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/perforce.rb +216 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/star_team.rb +104 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/subversion.rb +397 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/subversion_log_parser.rb +165 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/tempdir.rb +17 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/time_ext.rb +11 -0
- data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/version.rb +13 -0
- data/vendor/ruby-feedparser-0.5-stripped/README +14 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser.rb +28 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/feedparser.rb +300 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/filesizes.rb +12 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/html-output.rb +126 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/html2text-parser.rb +409 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/rexml_patch.rb +28 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/sgml-parser.rb +332 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/text-output.rb +83 -0
- data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/textconverters.rb +120 -0
- 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
|