rscm 0.3.6 → 0.3.7

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