rscm 0.3.6 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,5 +1,12 @@
1
1
  = RSCM Changelog
2
2
 
3
+ == Version 0.3.7
4
+
5
+ This release improves polling of revisions (changesets) and improves ClearCase support
6
+
7
+ * Added RSCM.Base.poll_new_revisions (moved from DamageControl)
8
+ * Rewrote the ClearCase adapter. There are no tests for ClearCase yet - tested manually.
9
+
3
10
  == Version 0.3.6
4
11
 
5
12
  Bugfix release
data/README CHANGED
@@ -1,4 +1,4 @@
1
- = RSCM - Ruby Source Control Management (0.3.6)
1
+ = RSCM - Ruby Source Control Management (0.3.7)
2
2
 
3
3
  RSCM is to SCM what DBI/JDBC/ODBC are to databases - an SCM-independent API for accessing different SCMs. The features are roughly:
4
4
 
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ require 'meta_project'
10
10
 
11
11
  PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
12
12
  PKG_NAME = 'rscm'
13
- PKG_VERSION = '0.3.6' + PKG_BUILD
13
+ PKG_VERSION = '0.3.7' + PKG_BUILD
14
14
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
15
 
16
16
  desc "Default Task"
@@ -0,0 +1,77 @@
1
+ module RSCM
2
+ class Base
3
+ attr_accessor :logger
4
+
5
+ TWO_WEEKS_AGO = 2*7*24*60*60
6
+ THIRTY_TWO_WEEKS_AGO = TWO_WEEKS_AGO * 16
7
+
8
+ # Polls new revisions for since +last_revision+,
9
+ # or if +last_revision+ is nil, polls since 'now' - +seconds_before_now+.
10
+ # If no revisions are found AND the poll was using +seconds_before_now+
11
+ # (i.e. it's the first poll, and no revisions were found),
12
+ # calls itself recursively with twice the +seconds_before_now+.
13
+ # This happens until revisions are found, ot until the +seconds_before_now+
14
+ # Exceeds 32 weeks, which means it's probably not worth looking further in
15
+ # the past, the scm is either completely idle or not yet active.
16
+ def poll_new_revisions(latest_revision=nil, seconds_before_now=TWO_WEEKS_AGO, max_time_before_now=THIRTY_TWO_WEEKS_AGO)
17
+ max_past = Time.new.utc - max_time_before_now
18
+
19
+ if(!central_exists?)
20
+ logger.info "Not polling for revisions - central scm repo doesn't seem to exist" if logger
21
+ return []
22
+ end
23
+
24
+ # Default value for start time (in case there are no detected revisions yet)
25
+ from = Time.new.utc - seconds_before_now
26
+ if(latest_revision)
27
+ from = latest_revision.identifier
28
+ else
29
+ if(from < max_past)
30
+ logger.info "Checked for revisions as far back as #{max_past}. There were none, so we give up." if logger
31
+ return []
32
+ else
33
+ logger.info "Latest revision is not known. Checking for revisions since: #{from}" if logger
34
+ end
35
+ end
36
+
37
+ logger.info "Polling revisions after #{from} (#{from.class.name})" if logger
38
+
39
+ revisions = revisions(from)
40
+ if(revisions.empty?)
41
+ logger.info "No new revisions after #{from}" if logger
42
+ unless(latest_revision)
43
+ double_seconds_before_now = 2*seconds_before_now
44
+ logger.info "Last revision still not found, checking since #{double_seconds_before_now.ago}" if logger
45
+ return poll_new_revisions(project, double_seconds_before_now, max_time_before_now)
46
+ end
47
+ else
48
+ logger.info "There were #{revisions.length} new revision(s) after #{from}" if logger
49
+ end
50
+
51
+ if(!revisions.empty? && !transactional?)
52
+ # We're dealing with a non-transactional SCM (like CVS/StarTeam/ClearCase,
53
+ # unlike Subversion/Monotone). Sleep a little, get the revisions again.
54
+ # When the revisions are not changing, we can consider the last commit done
55
+ # and the quiet period elapsed. This is not 100% failsafe, but will work
56
+ # under most circumstances. In the worst case, we'll miss some files in
57
+ # the revisions for really slow commits, but they will be part of the next
58
+ # revision (on next poll).
59
+ commit_in_progress = true
60
+ quiet_period = project.quiet_period || DEFAULT_QUIET_PERIOD
61
+ while(commit_in_progress)
62
+ logger.info "Sleeping for #{quiet_period} seconds because #{visual_name} is not transactional." if logger
63
+
64
+ sleep(quiet_period)
65
+ previous_revisions = revisions
66
+ revisions = revisions(from)
67
+ commit_in_progress = revisions != previous_revisions
68
+ if(commit_in_progress)
69
+ logger.info "Commit still in progress." if logger
70
+ end
71
+ end
72
+ logger.info "Quiet period elapsed" if logger
73
+ end
74
+ return revisions
75
+ end
76
+ end
77
+ end
@@ -1,81 +1,159 @@
1
1
  require 'rscm/base'
2
2
  require 'rscm/path_converter'
3
3
  require 'fileutils'
4
+ require 'tempfile'
4
5
 
5
6
  module RSCM
6
- class ClearCase < Base
7
-
8
- LOG_FORMAT = "Developer:%u\\nTime:%Nd\\nExtendedName:%Xn\\nVersionId:%Vn\\nPreviousVersionId:%PVn\\nElementName:%En\\nOID:%On\\nO:%o\\nMessage:%Nc\\n------------------------------------------\\n"
9
-
10
- def revisions(checkout_dir, from_identifier, to_identifier=Time.infinity)
11
- result = Revisions.new
12
- with_working_dir(checkout_dir) do
13
- since = from_identifier.strftime("%d-%b-%Y.%H:%M:%S")
14
- cleartool("lshistory -recurse -nco -since #{since} -fmt #{LOG_FORMAT}") do |io|
15
- io.each_line {|l| puts l}
16
- revisions << Revision.new()
17
- end
18
- end
19
- result
20
- end
21
-
22
- def diff(checkout_dir, change)
23
- with_working_dir(checkout_dir) do
24
- cleartool("diff -diff_format #{change.path}@@#{change.previous_native_revision_identifier} #{change.path}@@#{change.revision}")
25
- end
26
- end
27
-
28
- def checked_out?(checkout_dir)
29
- File.exists?("#{checkout_dir}")
30
- end
31
-
32
- def uptodate?(checkout_dir, from_identifier)
33
- if (!checked_out?(checkout_dir))
34
- false
35
- else
36
- with_working_dir(checkout_dir) do
37
- false
38
- end
39
- end
40
- end
41
-
42
- def commit(checkout_dir, message)
43
-
44
- end
45
-
46
- def import
47
- # clearfsimport -preview -recurse -nsetevent <from> <to>
48
- end
49
-
50
- protected
51
-
52
- # Checks out silently. Called by superclass' checkout.
53
- def checkout_silent(checkout_dir, to_identifier)
54
- with_working_dir(checkout_dir) do
55
- cleartool("update .") { |io|
56
- #io.each_line {|l| puts l}
57
- }
58
- end
59
- end
60
-
61
- # Administrative files that should be ignored when counting files.
62
- def ignore_paths
63
- return [/.*\.updt/]
64
- end
65
-
66
- private
67
-
68
- def cleartool(cleartool_cmd)
69
- cmd = "cleartool #{cleartool_cmd}"
70
- Better.popen(cmd, "r+") do |io|
71
- if(block_given?)
72
- return(yield(io))
73
- else
74
- # just read stdout so we can exit
75
- io.read
76
- end
77
- end
78
- end
79
-
80
- end
81
- end
7
+ class ClearCase < Base
8
+ register self
9
+
10
+ LOG_FORMAT = "- !ruby/object:RSCM::RevisionFile\\n developer: %u\\n time: \\\"%Nd\\\"\\n native_revision_identifier: %Vn\\n previous_native_revision_identifier: %PVn\\n path: %En\\n status: %o\\n message: \\\"%Nc\\\"\\n\\n"
11
+ TIME_FORMAT = "%d-%b-%Y.%H:%M:%S"
12
+ MAGIC = "9q8w7e6r5t4y"
13
+ STATUSES = {
14
+ "checkin" => RevisionFile::MODIFIED,
15
+ "mkelem" => RevisionFile::ADDED,
16
+ "rmelem" => RevisionFile::DELETED,
17
+ }
18
+
19
+ attr_accessor :stream, :stgloc, :tag, :config_spec
20
+
21
+ def initialize(stream, stgloc, tag, config_spec)
22
+ @stream, @stgloc, @tag, @config_spec = stream, stgloc, tag, config_spec
23
+ end
24
+
25
+ def revisions(from_identifier, to_identifier=Time.infinity, relative_path=nil)
26
+ checkout unless checked_out?
27
+
28
+ rules = load_rules
29
+ vob = vob(rules[0])
30
+
31
+ result = Revisions.new
32
+ with_working_dir(checkout_dir) do
33
+ since = (from_identifier + 1).strftime(TIME_FORMAT)
34
+ cmd = "cleartool lshistory -recurse -nco -since #{since} -fmt \"#{LOG_FORMAT}\" -pname #{vob}"
35
+ Better.popen(cmd) do |io|
36
+ # escape all quotes, except the one at the beginning and end. this is a bit ugly...
37
+ raw_yaml = io.read
38
+ fixed_yaml = raw_yaml.gsub(/^ message: \"/, " message: #{MAGIC}")
39
+ fixed_yaml = fixed_yaml.gsub(/\"\n\n/, "#{MAGIC}\n\n")
40
+ fixed_yaml = fixed_yaml.gsub(/\"/, "\\\"")
41
+ fixed_yaml = fixed_yaml.gsub(MAGIC, "\"")
42
+
43
+ files = YAML.load(fixed_yaml)
44
+ files.each do |file|
45
+ file.path.gsub!(/\\/, "/")
46
+ file.status = STATUSES[file.status]
47
+ rev = revision(file.native_revision_identifier)
48
+ if(rev && matches_load_rules?(rules, file.path))
49
+ file.native_revision_identifier = rev
50
+ file.previous_native_revision_identifier = revision(file.previous_native_revision_identifier)
51
+ t = file.time
52
+ # the time now has escaped quotes..
53
+ file.time = Time.utc(t[2..5],t[6..7],t[8..9],t[11..12],t[13..14],t[15..16])
54
+ file.message.strip!
55
+ result.add(file)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ result
61
+ end
62
+
63
+ def checked_out?
64
+ File.exist?(checkout_dir)
65
+ end
66
+
67
+ def destroy_working_copy
68
+ Better.popen("cleartool rmview #{checkout_dir}") do |io|
69
+ io.read
70
+ end
71
+ end
72
+
73
+ protected
74
+
75
+ def checkout_silent(to_identifier=nil)
76
+ if(checked_out?)
77
+ with_working_dir(checkout_dir) do
78
+ Better.popen("cleartool update .") do |io|
79
+ io.read
80
+ end
81
+ end
82
+ else
83
+ # Create view (working copy)
84
+ mkview_cmd = "cleartool mkview -snapshot -stream #{@stream} -stgloc #{@stgloc} -tag #{@tag} #{@checkout_dir}"
85
+ Better.popen(mkview_cmd) do |io|
86
+ puts io.read
87
+ end
88
+
89
+ # Set load rules (by setting config spec)
90
+ Dir.chdir(checkout_dir) do
91
+ # tempfile is broken on windows (!!)
92
+ cfg_spec_file = "__rscm.cfgspec"
93
+ config_spec_file = File.open(cfg_spec_file, "w") do |io|
94
+ io.write(@config_spec)
95
+ end
96
+
97
+ setcs_cmd = "cleartool setcs #{cfg_spec_file}"
98
+ Better.popen(setcs_cmd, "w") do |io|
99
+ io.write "yes\n"
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ # Administrative files that should be ignored when counting files.
106
+ def ignore_paths
107
+ return [/.*\.updt/]
108
+ end
109
+
110
+ private
111
+
112
+ def vob(rule)
113
+ if(rule =~ /[\\\/]*([\w]*)/)
114
+ $1
115
+ else
116
+ nil
117
+ end
118
+ end
119
+
120
+ # What's loaded into view
121
+ def load_rules
122
+ result = []
123
+ config_spec do |io|
124
+ io.each_line do |line|
125
+ if(line =~ /^load[\s]*(.*)$/)
126
+ return result << $1
127
+ end
128
+ end
129
+ end
130
+ result
131
+ end
132
+
133
+ def config_spec
134
+ Dir.chdir(checkout_dir) do
135
+ catcs_cmd = "cleartool catcs"
136
+ Better.popen(catcs_cmd) do |io|
137
+ yield io
138
+ end
139
+ end
140
+ end
141
+
142
+ def revision(s)
143
+ if(s =~ /.*\\([\d]*)/)
144
+ $1.to_i
145
+ else
146
+ nil
147
+ end
148
+ end
149
+
150
+ def matches_load_rules?(rules, path)
151
+ rules.each do |rule|
152
+ rule.gsub!(/\\/, "/")
153
+ return true if path =~ /#{rule[1..-1]}/
154
+ end
155
+ false
156
+ end
157
+
158
+ end
159
+ end
@@ -4,7 +4,7 @@ require 'rscm'
4
4
 
5
5
  module RSCM
6
6
  class Monotone < Base
7
- register self
7
+ # register self
8
8
 
9
9
  ann :description => "Database file"
10
10
  attr_accessor :db_file
@@ -76,13 +76,8 @@ module RSCM
76
76
  absolute_path = PathConverter.nativepath_to_filepath(native_absolute_path)
77
77
  if(File.exist?(absolute_path) && !File.directory?(absolute_path))
78
78
  native_checkout_dir = PathConverter.filepath_to_nativepath(@checkout_dir, false)
79
- relative_path = nil
80
- if WINDOWS
81
- relative_path = native_absolute_path[native_checkout_dir.length+1..-1].chomp
82
- relative_path = relative_path.gsub(/\\/, "/")
83
- else
84
- relative_path = absolute_path
85
- end
79
+ relative_path = native_absolute_path[native_checkout_dir.length+1..-1].chomp
80
+ relative_path = relative_path.gsub(/\\/, "/")
86
81
  checked_out_files << relative_path
87
82
  yield relative_path if block_given?
88
83
  end
data/lib/rscm.rb CHANGED
@@ -5,6 +5,7 @@ require 'rscm/logging'
5
5
  require 'rscm/better'
6
6
  require 'rscm/base'
7
7
  require 'rscm/revision'
8
+ require 'rscm/revision_poller'
8
9
  require 'rscm/revision_file'
9
10
  require 'rscm/historic_file'
10
11
  require 'rscm/time_ext'
@@ -5,9 +5,10 @@ module RSCM
5
5
  class BaseTest < Test::Unit::TestCase
6
6
  def test_should_load_all_scm_classes
7
7
  expected_scms_classes = [
8
+ ClearCase,
8
9
  Cvs,
9
10
  # Darcs,
10
- Monotone,
11
+ # Monotone,
11
12
  # Mooky,
12
13
  Perforce,
13
14
  # StarTeam,
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: rscm
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.6
7
- date: 2005-10-05 00:00:00 -04:00
6
+ version: 0.3.7
7
+ date: 2005-10-11 00:00:00 -04:00
8
8
  summary: "RSCM - Ruby Source Control Management"
9
9
  require_paths:
10
10
  - lib
@@ -47,6 +47,7 @@ files:
47
47
  - lib/rscm/path_converter.rb
48
48
  - lib/rscm/revision.rb
49
49
  - lib/rscm/revision_file.rb
50
+ - lib/rscm/revision_poller.rb
50
51
  - lib/rscm/scm
51
52
  - lib/rscm/tempdir.rb
52
53
  - lib/rscm/time_ext.rb
@@ -78,9 +79,9 @@ files:
78
79
  - test/rscm/mockit_test.rb
79
80
  - test/rscm/parser_test.rb
80
81
  - test/rscm/path_converter_test.rb
81
- - test/rscm/revisions.yaml
82
82
  - test/rscm/revision_fixture.rb
83
83
  - test/rscm/revision_test.rb
84
+ - test/rscm/revisions.yaml
84
85
  - test/rscm/scm
85
86
  - test/rscm/scm/clearcase.log
86
87
  - test/rscm/scm/clearcase_test.rb