rscm 0.2.1.1404 → 0.3.0

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 (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