whistle 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|