rscm 0.2.1.1404 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README +34 -23
  2. data/Rakefile +24 -29
  3. data/bin/touch.exe +0 -0
  4. data/lib/rscm.rb +6 -3
  5. data/lib/rscm/annotations.rb +26 -7
  6. data/lib/rscm/{abstract_scm.rb → base.rb} +109 -71
  7. data/lib/rscm/better.rb +16 -0
  8. data/lib/rscm/logging.rb +11 -5
  9. data/lib/rscm/path_converter.rb +9 -16
  10. data/lib/rscm/revision.rb +201 -0
  11. data/lib/rscm/revision_file.rb +71 -0
  12. data/lib/rscm/scm/clearcase.rb +7 -7
  13. data/lib/rscm/scm/cvs.rb +69 -70
  14. data/lib/rscm/scm/cvs_log_parser.rb +29 -29
  15. data/lib/rscm/scm/darcs.rb +82 -34
  16. data/lib/rscm/scm/darcs_log_parser.rb +65 -0
  17. data/lib/rscm/scm/monotone.rb +249 -77
  18. data/lib/rscm/scm/monotone_log_parser.rb +57 -43
  19. data/lib/rscm/scm/mooky.rb +3 -3
  20. data/lib/rscm/scm/perforce.rb +196 -134
  21. data/lib/rscm/scm/star_team.rb +10 -10
  22. data/lib/rscm/scm/subversion.rb +106 -77
  23. data/lib/rscm/scm/subversion_log_parser.rb +76 -47
  24. data/lib/rscm/time_ext.rb +2 -116
  25. data/test/rscm/annotations_test.rb +15 -2
  26. data/test/rscm/{abstract_scm_test.rb → base_test.rb} +3 -3
  27. data/test/rscm/difftool_test.rb +9 -3
  28. data/test/rscm/generic_scm_tests.rb +195 -124
  29. data/test/rscm/revision_fixture.rb +20 -0
  30. data/test/rscm/revision_test.rb +129 -0
  31. data/test/rscm/{changesets.yaml → revisions.yaml} +10 -10
  32. data/test/rscm/scm/clearcase.log +608 -0
  33. data/test/rscm/scm/clearcase_test.rb +39 -0
  34. data/test/rscm/scm/cvs_log_parser_test.rb +73 -73
  35. data/test/rscm/scm/cvs_test.rb +1 -1
  36. data/test/rscm/scm/darcs_log_parser_test.rb +171 -0
  37. data/test/rscm/scm/monotone_log_parser_test.rb +49 -31
  38. data/test/rscm/scm/monotone_test.rb +3 -2
  39. data/test/rscm/scm/p4client_test.rb +33 -0
  40. data/test/rscm/scm/perforce_test.rb +25 -3
  41. data/test/rscm/scm/star_team.rb +9 -9
  42. data/test/rscm/scm/subversion_log_parser_test.rb +107 -47
  43. metadata +17 -13
  44. data/lib/multipart.rb +0 -95
  45. data/lib/rscm/RSS.txt +0 -41
  46. data/lib/rscm/changes.rb +0 -268
  47. data/lib/rscm/example.yaml +0 -21
  48. data/lib/rubyforge_file_publisher.rb +0 -176
  49. data/test/rscm/changes_fixture.rb +0 -20
  50. data/test/rscm/changes_test.rb +0 -129
@@ -1,4 +1,4 @@
1
- require 'rscm/changes'
1
+ require 'rscm/revision'
2
2
  require 'rscm/abstract_log_parser'
3
3
 
4
4
  require 'ftools'
@@ -17,19 +17,19 @@ module RSCM
17
17
  @log = ""
18
18
  end
19
19
 
20
- def parse_changesets
21
- changesets = ChangeSets.new
20
+ def parse_revisions
21
+ revisions = Revisions.new
22
22
  while(log_entry = next_log_entry)
23
23
  @log<<log_entry
24
24
  @log<<""
25
25
  begin
26
- parse_changes(log_entry, changesets)
26
+ parse_files(log_entry, revisions)
27
27
  rescue Exception => e
28
28
  $stderr.puts("could not parse log entry: #{log_entry}\ndue to: #{e.message}\n\t")
29
29
  $stderr.puts(e.backtrace.join("\n\t"))
30
30
  end
31
31
  end
32
- changesets.sort!
32
+ revisions.sort!
33
33
  end
34
34
 
35
35
  def next_log_entry
@@ -48,20 +48,20 @@ module RSCM
48
48
  entries
49
49
  end
50
50
 
51
- def parse_changes(log_entry, changesets)
51
+ def parse_files(log_entry, revisions)
52
52
  entries = split_entries(log_entry)
53
53
 
54
54
  entries[1..entries.length].each do |entry|
55
- change = parse_change(entry)
56
- next if change.nil?
57
- change.path = parse_path(entries[0])
55
+ file = parse_file(entry)
56
+ next if file.nil?
57
+ file.path = parse_path(entries[0])
58
58
 
59
- change.status = Change::ADDED if change.revision =~ /1\.1$/
59
+ file.status = RevisionFile::ADDED if file.native_revision_identifier =~ /1\.1$/
60
60
 
61
- changeset = changesets.add(change)
62
- # CVS doesn't have revision for changesets, use
61
+ revision = revisions.add(file)
62
+ # CVS doesn't have revision for revisions, use
63
63
  # Fisheye-style revision
64
- # changeset.revision = "MAIN:#{change.developer}:#{change.time.utc.strftime('%Y%m%d%H%M%S')}" if changeset
64
+ # revision.native_revision_identifier = "MAIN:#{file.developer}:#{file.time.utc.strftime('%Y%m%d%H%M%S')}" if revision
65
65
  end
66
66
  nil
67
67
  end
@@ -82,39 +82,39 @@ module RSCM
82
82
  convert_all_slashes_to_forward_slashes(file).gsub(/^#{cvspath}\/#{cvsmodule}\//, "")
83
83
  end
84
84
 
85
- def parse_change(change_entry)
86
- raise "can't parse: #{change_entry}" if change_entry =~ REVISION_SEPARATOR
85
+ def parse_file(file_entry)
86
+ raise "can't parse: #{file_entry}" if file_entry =~ REVISION_SEPARATOR
87
87
 
88
- change_entry_lines = change_entry.split(/\r?\n/)
89
- change = Change.new
88
+ file_entry_lines = file_entry.split(/\r?\n/)
89
+ file = RevisionFile.new
90
90
 
91
- change.revision = extract_match(change_entry_lines[0], /revision (.*)$/)
91
+ file.native_revision_identifier = extract_match(file_entry_lines[0], /revision (.*)$/)
92
92
 
93
- change.previous_revision = determine_previous_revision(change.revision)
94
- change.time = parse_cvs_time(extract_required_match(change_entry_lines[1], /date: (.*?)(;|$)/))
95
- change.developer = extract_match(change_entry_lines[1], /author: (.*?);/)
93
+ file.previous_native_revision_identifier = determine_previous_native_revision_identifier(file.native_revision_identifier)
94
+ file.time = parse_cvs_time(extract_required_match(file_entry_lines[1], /date: (.*?)(;|$)/))
95
+ file.developer = extract_match(file_entry_lines[1], /author: (.*?);/)
96
96
 
97
- state = extract_match(change_entry_lines[1], /state: (.*?);/)
98
- change.status = STATES[state]
97
+ state = extract_match(file_entry_lines[1], /state: (.*?);/)
98
+ file.status = STATES[state]
99
99
 
100
100
  message_start = 2
101
101
  branches = nil
102
- if(change_entry_lines[2] =~ /^branches:\s+(.*);/)
102
+ if(file_entry_lines[2] =~ /^branches:\s+(.*);/)
103
103
  message_start = 3
104
104
  branches = $1
105
105
  end
106
106
 
107
- change.message = change_entry_lines[message_start..-1].join("\n")
107
+ file.message = file_entry_lines[message_start..-1].join("\n")
108
108
 
109
109
  # Ignore the initial revision from import - we will have two of them
110
- if(change.message == "Initial revision" && branches == "1.1.1")
110
+ if(file.message == "Initial revision" && branches == "1.1.1")
111
111
  return nil
112
112
  end
113
113
 
114
- change
114
+ file
115
115
  end
116
116
 
117
- def determine_previous_revision(revision)
117
+ def determine_previous_native_revision_identifier(revision)
118
118
  if revision =~ /(.*)\.(.*)/
119
119
  big_version_number = $1
120
120
  small_version_number = $2.to_i
@@ -154,7 +154,7 @@ module RSCM
154
154
  # The state field is "Exp" both for added and modified files. retards!
155
155
  # We need some additional logic to figure out whether it is added or not.
156
156
  # Maybe look at the revision. (1.1 means new I think. - deal with it later)
157
- STATES = {"dead" => Change::DELETED, "Exp" => Change::MODIFIED} unless defined? STATES
157
+ STATES = {"dead" => RevisionFile::DELETED, "Exp" => RevisionFile::MODIFIED} unless defined? STATES
158
158
 
159
159
  end
160
160
 
@@ -3,8 +3,8 @@ require 'fileutils'
3
3
  require 'rscm'
4
4
 
5
5
  module RSCM
6
- class Darcs < AbstractSCM
7
- register self
6
+ class Darcs < Base
7
+ #register self
8
8
 
9
9
  ann :description => "Directory"
10
10
  attr_accessor :dir
@@ -17,56 +17,104 @@ module RSCM
17
17
  "Darcs"
18
18
  end
19
19
 
20
- def create
20
+ def can_create_central?
21
+ true
22
+ end
23
+
24
+ def create_central
21
25
  with_working_dir(@dir) do
22
- IO.popen("darcs initialize") do |stdout|
23
- stdout.each_line do |line|
24
- yield line if block_given?
25
- end
26
- end
26
+ darcs("initialize")
27
27
  end
28
28
  end
29
29
 
30
- def import(dir, message)
30
+ def import_central(dir, message)
31
31
  ENV["EMAIL"] = "dcontrol@codehaus.org"
32
32
  FileUtils.cp_r(Dir.glob("#{dir}/*"), @dir)
33
33
  with_working_dir(@dir) do
34
- puts "IN::::: #{@dir}"
35
- cmd = "darcs add --recursive ."
36
- puts cmd
37
- IO.popen(cmd) do |stdout|
38
- stdout.each_line do |line|
39
- yield line if block_given?
40
- end
41
- end
42
- puts $?
34
+ darcs("add --recursive .")
35
+
43
36
  logfile = Tempfile.new("darcs_logfile")
44
- logfile.print(message)
37
+ logfile.print("something nice\n")
38
+ logfile.print(message + "\n")
45
39
  logfile.close
46
40
 
47
- cmd = "darcs record --all --patch-name \"something nice\" --logfile #{PathConverter.filepath_to_nativepath(logfile.path, false)}"
48
- puts cmd
49
- IO.popen(cmd) do |stdout|
50
- stdout.each_line do |line|
51
- yield line if block_given?
41
+ darcs("record --all --logfile #{PathConverter.filepath_to_nativepath(logfile.path, false)}")
42
+ end
43
+ end
44
+
45
+ def commit(message)
46
+ logfile = Tempfile.new("darcs_logfile")
47
+ logfile.print("something nice\n")
48
+ logfile.print(message + "\n")
49
+ logfile.close
50
+
51
+ with_working_dir(@checkout_dir) do
52
+ darcs("record --all --logfile #{PathConverter.filepath_to_nativepath(logfile.path, false)}")
53
+ end
54
+ end
55
+
56
+ def add(relative_filename)
57
+ with_working_dir(@checkout_dir) do
58
+ darcs("add #{relative_filename}")
59
+ end
60
+ end
61
+
62
+ def checked_out?
63
+ File.exists?("#{@checkout_dir}/_darcs")
64
+ end
65
+
66
+ def uptodate?(from_identifier)
67
+ if (!checked_out?(@checkout_dir))
68
+ false
69
+ else
70
+ with_working_dir(@checkout_dir) do
71
+ darcs("pull --dry-run #{@dir}") do |io|
72
+ io.each_line do |line|
73
+ if (line =~ /No remote changes to pull in!/)
74
+ true
75
+ else
76
+ false
77
+ end
78
+ end
52
79
  end
53
80
  end
54
- puts $?
55
81
  end
56
82
  end
57
83
 
58
- def checkout(checkout_dir) # :yield: file
84
+ def revisions(from_identifier, to_identifier=Time.infinity)
85
+ from_identifier = Time.epoch if from_identifier.nil?
86
+ to_identifier = Time.infinity if to_identifier.nil?
87
+ with_working_dir(@checkout_dir) do
88
+ darcs("changes --summary --xml-output") do |stdout|
89
+ DarcsLogParser.new.parse_revisions(stdout, from_identifier, to_identifier)
90
+ end
91
+ end
92
+ end
93
+
94
+ protected
95
+
96
+ def checkout_silent(to_identifier) # :yield: file
59
97
  with_working_dir(File.dirname(checkout_dir)) do
60
- cmd = "darcs get --verbose --repo-name #{File.basename(checkout_dir)} #{@dir}"
61
- puts cmd
62
- IO.popen(cmd) do |stdout|
63
- stdout.each_line do |line|
64
- puts line
65
- yield line if block_given?
66
- end
98
+ darcs("get --repo-name #{File.basename(checkout_dir)} #{@dir}")
99
+ end
100
+ end
101
+
102
+ def ignore_paths
103
+ return [/_darcs/]
104
+ end
105
+
106
+ private
107
+
108
+ def darcs(darcs_cmd)
109
+ cmd = "darcs #{darcs_cmd}"
110
+
111
+ Better.popen(cmd, "r+") do |io|
112
+ if(block_given?)
113
+ return(yield(io))
114
+ else
115
+ io.read
67
116
  end
68
117
  end
69
- puts $?
70
118
  end
71
119
  end
72
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
+
@@ -1,8 +1,9 @@
1
+ require 'rscm/line_editor'
1
2
  require 'fileutils'
2
3
  require 'rscm'
3
4
 
4
5
  module RSCM
5
- class Monotone < AbstractSCM
6
+ class Monotone < Base
6
7
  register self
7
8
 
8
9
  ann :description => "Database file"
@@ -20,101 +21,130 @@ module RSCM
20
21
  ann :description => "Keys file"
21
22
  attr_accessor :keys_file
22
23
 
23
- def initialize(db_file="MT.db", branch="", key="", passphrase="", keys_file="")
24
- @db_file = File.expand_path(db_file)
24
+ ann :description => "Server"
25
+ attr_accessor :server
26
+
27
+ def initialize(branch=nil, key=nil, passphrase=nil, keys_file=nil, server=nil, central_checkout_dir=nil)
25
28
  @branch = branch
26
29
  @key = key
27
30
  @passphrase = passphrase
28
31
  @keys_file = keys_file
32
+ @server = server
33
+ @central_checkout_dir = File.expand_path(central_checkout_dir) unless central_checkout_dir.nil?
29
34
  end
30
35
 
31
36
  def name
32
37
  "Monotone"
33
38
  end
34
39
 
35
- def add(checkout_dir, relative_filename)
36
- with_working_dir(checkout_dir) do
37
- monotone("add #{relative_filename}")
40
+ def add(relative_filename)
41
+ db = db(@checkout_dir)
42
+ with_working_dir(@checkout_dir) do
43
+ monotone("add #{relative_filename}", db)
38
44
  end
39
45
  end
40
46
 
41
- def create
42
- FileUtils.mkdir_p(File.dirname(@db_file))
43
- monotone("db init")
44
- monotone("read") do |io|
45
- io.write(File.open(@keys_file).read)
46
- io.close_write
47
- end
47
+ def can_create_central?
48
+ @server == "localhost" && !@central_checkout_dir.nil?
48
49
  end
49
-
50
- def transactional?
51
- true
50
+
51
+ def central_exists?
52
+ @central_checkout_dir && @serve_pid
52
53
  end
53
54
 
54
- def import(dir, message)
55
- dir = File.expand_path(dir)
56
-
57
- # post 0.17, this can be "cd dir && cmd add ."
55
+ def create_central
56
+ init(@central_checkout_dir)
57
+ # create empty working copy
58
+ dir = PathConverter.filepath_to_nativepath(@central_checkout_dir, false)
59
+ # set up a working copy
60
+ monotone("setup #{dir}")
61
+ start_serve
62
+ end
63
+
64
+ def start_serve
65
+ mode = File::CREAT|File::WRONLY
66
+ if File.exist?(rcfile)
67
+ mode = File::APPEND|File::WRONLY
68
+ end
58
69
 
59
- files = Dir["#{dir}/*"]
60
- relative_paths_to_add = to_relative(dir, files)
70
+ begin
71
+ File.open(rcfile, mode) do |file|
72
+ file.puts("function get_netsync_anonymous_read_permitted(collection)")
73
+ file.puts(" return true")
74
+ file.puts("end")
75
+ end
76
+ rescue => e
77
+ puts e.message
78
+ puts e.backtrace.join("\n")
79
+ raise "Didn't have permission to write to #{rcfile}."
80
+ end
61
81
 
62
- with_working_dir(dir) do
63
- monotone("add #{relative_paths_to_add.join(' ')}")
64
- monotone("commit '#{message}'", @branch, @key) do |io|
82
+ @serve_pid = fork do
83
+ #Signal.trap("HUP") { puts "Monotone server shutting down..."; exit }
84
+ monotone("serve --rcfile=\"#{rcfile}\" #{@server} #{@branch}", db(@central_checkout_dir)) do |io|
85
+ puts "PASSPHRASE: #{@passphrase}"
65
86
  io.puts(@passphrase)
66
87
  io.close_write
67
- io.read
68
88
  end
69
89
  end
90
+ Process.detach(@serve_pid)
70
91
  end
71
-
72
- def checked_out?(checkout_dir)
73
- File.exists?("#{checkout_dir}/MT")
92
+
93
+ def stop_serve
94
+ Process.kill("HUP", @serve_pid) if @serve_pid
95
+ Process.waitpid2(@serve_pid) if @serve_pid
96
+ @serve_pid = nil
97
+ end
98
+
99
+ def destroy_central
100
+ stop_serve
101
+ FileUtils.rm_rf(@central_checkout_dir) if File.exist?(@central_checkout_dir)
102
+ FileUtils.rm(db(@central_checkout_dir)) if File.exist?(db(@central_checkout_dir))
103
+ puts "Destroyed Monotone server"
104
+ end
105
+
106
+ def transactional?
107
+ true
74
108
  end
75
109
 
76
- def uptodate?(checkout_dir, from_identifier)
77
- if (!checked_out?(checkout_dir))
78
- false
79
- else
80
- lr = local_revision(checkout_dir)
81
- hr = head_revision(checkout_dir)
82
- lr == hr
110
+ def import_central(dir, message)
111
+ cp_r(Dir["#{dir}/*"], @central_checkout_dir)
112
+ with_working_dir(@central_checkout_dir) do
113
+ monotone("add .")
114
+ commit_in_dir(message, @central_checkout_dir)
83
115
  end
84
116
  end
85
117
 
86
- def local_revision(checkout_dir)
87
- local_revision = nil
88
- rev_file = File.expand_path("#{checkout_dir}/MT/revision")
89
- local_revision = File.open(rev_file).read.strip
90
- local_revision
118
+ def checked_out?
119
+ mt = File.expand_path("#{@checkout_dir}/MT")
120
+ File.exists?(mt)
91
121
  end
92
-
93
- def head_revision(checkout_dir)
94
- # FIXME: this will grab last head if heads are not merged.
95
- head_revision = nil
96
- monotone("heads", @branch) do |stdout|
97
- stdout.each_line do |line|
98
- next if (line =~ /^monotone:/)
99
- head_revision = line.split(" ")[0]
100
- end
122
+
123
+ def uptodate?(identifier=nil)
124
+ if (!checked_out?)
125
+ false
126
+ else
127
+ pull
128
+
129
+ rev = identifier ? identifier : head_revision
130
+ local_revision == rev
101
131
  end
102
- head_revision
103
132
  end
104
133
 
105
- def changesets(checkout_dir, from_identifier, to_identifier=Time.infinity)
106
- from_identifier = Time.epoch if from_identifier.nil?
134
+ def revisions(from_identifier, to_identifier=Time.infinity)
135
+ checkout(to_identifier)
107
136
  to_identifier = Time.infinity if to_identifier.nil?
108
137
  with_working_dir(checkout_dir) do
109
- monotone("log", @branch, @key) do |stdout|
110
- MonotoneLogParser.new.parse_changesets(stdout, from_identifier, to_identifier)
138
+ monotone("log") do |stdout|
139
+ MonotoneLogParser.new.parse_revisions(stdout, from_identifier, to_identifier)
111
140
  end
112
141
  end
113
142
  end
114
143
 
115
- def commit(checkout_dir, message)
116
- with_working_dir(checkout_dir) do
117
- monotone("commit '#{message}'", @branch, @key) do |io|
144
+ def commit(message)
145
+ commit_in_dir(message, @checkout_dir)
146
+ with_working_dir(@checkout_dir) do
147
+ monotone("push #{@server} #{@branch}") do |io|
118
148
  io.puts(@passphrase)
119
149
  io.close_write
120
150
  io.read
@@ -122,24 +152,54 @@ module RSCM
122
152
  end
123
153
  end
124
154
 
155
+ # http://www.venge.net/monotone/monotone.html#Hook-Reference
156
+ def install_trigger(trigger_command, install_dir)
157
+ stop_serve
158
+ if (WINDOWS)
159
+ install_win_trigger(trigger_comand, install_dir)
160
+ else
161
+ install_unix_trigger(trigger_command, install_dir)
162
+ end
163
+ start_serve
164
+ end
165
+
166
+ def trigger_installed?(trigger_command, install_dir)
167
+ File.exist?(rcfile)
168
+ end
169
+
170
+ def uninstall_trigger(trigger_command, install_dir)
171
+ stop_serve
172
+ File.delete(rcfile)
173
+ start_serve
174
+ end
175
+
176
+ def diff(change, &block)
177
+ checkout(change.revision)
178
+ with_working_dir(@checkout_dir) do
179
+ monotone("diff --revision=#{change.previous_native_revision_identifier} #{change.path}") do |stdout|
180
+ yield stdout
181
+ end
182
+ end
183
+ end
184
+
125
185
  protected
126
186
 
127
187
  # Checks out silently. Called by superclass' checkout.
128
- def checkout_silent(checkout_dir, to_identifier)
129
- checkout_dir = PathConverter.filepath_to_nativepath(checkout_dir, false)
130
- if checked_out?(checkout_dir)
131
- with_working_dir(checkout_dir) do
132
- monotone("update")
133
- end
134
- else
135
- monotone("checkout #{checkout_dir}", @branch, @key) do |stdout|
136
- stdout.each_line do |line|
137
- # TODO: checkout prints nothing to stdout - may be fixed in a future monotone.
138
- # When/if it happens we may want to do a kosher implementation of checkout
139
- # to get yields as checkouts happen.
140
- yield line if block_given?
141
- end
142
- end
188
+ def checkout_silent(to_identifier)
189
+ # raise "Monotone doesn't support checkout to time. Please use identifiers instead." if to_identifier.is_a?(Time)
190
+ db_file = db(@checkout_dir)
191
+ if(!File.exist?(db_file))
192
+ init(@checkout_dir)
193
+ end
194
+
195
+ pull
196
+ checked_out = checked_out?
197
+
198
+ with_working_dir(@checkout_dir) do
199
+ monotone("checkout .", db_file, @branch) unless checked_out
200
+
201
+ selector = expand_selector(to_identifier)
202
+ monotone("update #{selector}", db_file)
143
203
  end
144
204
  end
145
205
 
@@ -149,12 +209,119 @@ module RSCM
149
209
  end
150
210
 
151
211
  private
212
+
213
+ def commit_in_dir(message, dir)
214
+ db_file = db(dir)
215
+ with_working_dir(dir) do
216
+ monotone("commit --message='#{message}'", db_file, @branch, @key) do |io|
217
+ io.puts(@passphrase)
218
+ io.close_write
219
+ io.read
220
+ end
221
+ end
222
+ end
223
+
224
+ def pull
225
+ db_file = db(@checkout_dir)
226
+ with_working_dir(@checkout_dir) do
227
+ # pull from the "central" server
228
+ if(@server)
229
+ monotone("pull #{@server} #{@branch}", db_file) do |io|
230
+ io.puts(@passphrase)
231
+ io.close_write
232
+ io.read
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ def db(checkout_dir)
239
+ PathConverter.filepath_to_nativepath(checkout_dir + ".db", false)
240
+ end
241
+
242
+ # Initialises a monotone database
243
+ #
244
+ def init(dir)
245
+ dir = File.expand_path(dir)
246
+ db_file = db(dir)
247
+ raise "Database #{db_file} already exists" if File.exist?(db_file)
248
+ FileUtils.mkdir_p(File.dirname(db_file))
249
+ # create database
250
+ monotone("db init", db_file)
251
+ # TODO: do a genkey
252
+ monotone("read", db_file) do |io|
253
+ io.write(File.open(@keys_file).read)
254
+ io.close_write
255
+ end
256
+ end
257
+
258
+ def install_unix_trigger(trigger_command, install_dir)
259
+ mode = File::CREAT|File::WRONLY
260
+ if File.exist?(rcfile)
261
+ mode = File::APPEND|File::WRONLY
262
+ end
263
+
264
+ begin
265
+ File.open(rcfile, mode) do |file|
266
+ file.puts("function note_commit(new_id, certs)")
267
+ execstr = "\"" + trigger_command.split.join("\",\"") + "\""
268
+ file.puts(" execute(#{execstr})")
269
+ file.puts("end")
270
+ end
271
+ rescue => e
272
+ puts e.message
273
+ puts e.backtrace.join("\n")
274
+ raise "Didn't have permission to write to #{rcfile}."
275
+ end
276
+
277
+ # push to the "central" server
278
+ # monotone("push #{@server} #{@branch}", db(@central_checkout_dir))
279
+ end
280
+
281
+ def rcfile
282
+ "#{@central_checkout_dir}/MT/monotonerc"
283
+ end
284
+
285
+ def local_revision
286
+ local_revision = nil
287
+ rev_file = File.expand_path("#{checkout_dir}/MT/revision")
288
+ local_revision = File.open(rev_file).read.strip
289
+ local_revision
290
+ end
291
+
292
+ def head_revision
293
+ # FIXME: this will grab last head if heads are not merged.
294
+ head_revision = nil
295
+ monotone("heads", db(@checkout_dir), @branch) do |stdout|
296
+ stdout.each_line do |line|
297
+ next if (line =~ /^monotone:/)
298
+ head_revision = line.split(" ")[0]
299
+ end
300
+ end
301
+ head_revision
302
+ end
303
+
304
+ # See http://www.venge.net/monotone/monotone.html#Selectors
305
+ # Also see docs for expand_selector in the same document
306
+ # Dates are formatted with strftime-style %F, which is of style 2005-28-02,
307
+ # which is very coarse grained. Date identifiers are therefore discouraged.
308
+ def expand_selector(identifier)
309
+ if(identifier.is_a?(Time))
310
+ # Won't work:
311
+ # "d:#{identifier.strftime('%Y-%m-%d')}"
312
+ ""
313
+ else
314
+ "i:#{identifier}"
315
+ end
316
+ end
152
317
 
153
- def monotone(monotone_cmd, branch=nil, key=nil)
318
+ def monotone(monotone_cmd, db_file=nil, branch=nil, key=nil)
319
+ db_opt = db_file ? "--db=\"#{db_file}\"" : ""
154
320
  branch_opt = branch ? "--branch=\"#{branch}\"" : ""
155
321
  key_opt = key ? "--key=\"#{key}\"" : ""
156
- cmd = "monotone --db=\"#{@db_file}\" #{branch_opt} #{key_opt} #{monotone_cmd}"
157
- safer_popen(cmd, "r+") do |io|
322
+ rcfile_opt = @rcfile ? "--rcfile=\"#{@rcfile}\"" : ""
323
+ cmd = "monotone #{db_opt} #{branch_opt} #{key_opt} #{rcfile_opt} #{monotone_cmd}"
324
+ Better.popen(cmd, "r+") do |io|
158
325
  if(block_given?)
159
326
  return(yield(io))
160
327
  else
@@ -163,6 +330,11 @@ module RSCM
163
330
  end
164
331
  end
165
332
  end
166
-
333
+
334
+ def monotone_date(time)
335
+ return nil unless time
336
+ time.utc.strftime("%Y-%m-%dT%H:%M:%S")
337
+ end
338
+
167
339
  end
168
340
  end