Class: SvnTransform

Inherits:
Object
  • Object
show all
Defined in:
lib/svn-transform.rb,
lib/svn-transform/dir.rb,
lib/svn-transform/file.rb,
lib/svn-transform/session.rb,
lib/svn-transform/transform/noop.rb,
lib/svn-transform/transform/newline.rb,
lib/svn-transform/transform/extension.rb,
lib/svn-transform/transform/props_to_yaml.rb

Defined Under Namespace

Modules: Transform Classes: Dir, File, Session

Constant Summary collapse

VERSION =
'0.2.0'
false
COMPARE_DIR =
'/tmp/svn-transform/compare'
COMPARE_OLD_DIR =
File.join(COMPARE_DIR, 'old')
COMPARE_NEW_DIR =
File.join(COMPARE_DIR, 'new')

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(in_repo_uri, out_repo_name = nil, options = {}) ⇒ SvnTransform

Setup and SvnTransform with in (existing) repository URI, a name for the out (transformed) repository, and options.

Parameters

in_repo_uri<String>

URI of existing repository(e.g. file:///home/jm81/repo, svn://localhost/repo)

out_repo_name<String>

Name only of out repository (e.g. “out”). See options for specifying full path (on local filesystem only)

Options

:username<String>

Username for in (existing) repository

:password<String>

Password for in (existing) repository

:out_repos_path<String>

Full path for out repository (defaults to “#:base_path/repo_#out_repo_name”)

:out_wc_path<String>

Full path for out working copy (used by SvnFixture; defaults to “#:base_path/wc_#out_repo_name”)



122
123
124
125
126
127
128
129
130
131
# File 'lib/svn-transform.rb', line 122

def initialize(in_repo_uri, out_repo_name = nil, options = {})
  @in_username = options[:username]
  @in_password = options[:password]
  @in_repo_uri = in_repo_uri
  @out_repo_name = out_repo_name
  @out_repos_path = options[:out_repos_path]
  @out_wc_path = options[:out_wc_path]
  @file_transforms = []
  @dir_transforms = []
end

Class Method Details

.co_compare(old_repo, new_repo, min_rev, max_rev) ⇒ Object

Compare checkouts. This takes much longer than .compare but can give a more accurate picture, because it is not affected by the changes in svn:entry properties (well, it actually just ignores the related files), the chance that entries are just in different order within the db file, or differences between the directory structure of the repo’s db folders.

It also has the advantage that the repositories can be remote.

Note that this method will destroy existing files in the COMPARE_DIR

Parameters

old_repo<String>

URI of original (in) repository.

new_repo<String>

URI of generated (out) repository.

min_rev<Integer>

Starting revision for comparison

max_rev<Integer>

Ending revision for comparison



64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/svn-transform.rb', line 64

def co_compare(old_repo, new_repo, min_rev, max_rev)
  FileUtils.rm_rf(COMPARE_OLD_DIR)
  FileUtils.rm_rf(COMPARE_NEW_DIR)

  rev = min_rev
  `svn co -r#{rev} "#{old_repo}" "#{COMPARE_OLD_DIR}"`
  `svn co -r#{rev} "#{new_repo}" "#{COMPARE_NEW_DIR}"`
  
  while rev <= max_rev
    co_compare_rev(rev)
    rev += 1
  end
end

.co_compare_rev(rev) ⇒ Object

Called by .co_compare, this checks out and compares the repositories at a signle revision. It prints out any differences.

Parameters

rev<Integer>

The revision to compare.

Returns

True, False

Whether the revisions are identical



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/svn-transform.rb', line 86

def co_compare_rev(rev)
  print "#{rev} " if PRINT_INFO
  `svn update -r#{rev} "#{COMPARE_OLD_DIR}"`
  `svn update -r#{rev} "#{COMPARE_NEW_DIR}"`
  ret = `diff --brief --exclude=entries -r "#{COMPARE_OLD_DIR}" "#{COMPARE_NEW_DIR}"`
  if ret.empty?
    return true
  else
    puts "\nREVISION #{rev}"
    puts ret
    puts ("-" * 70)
    return false
  end
end

.compare(old_dir, new_dir) ⇒ Object

Use diff to compare two repositories (on local file system) Where reasonable, this (or something like it) should be run to verify expected results. I recommend trying a direct copy first to ensure your original repo doesn’t have any featurs that SvnPropsToYaml won’t understand.

www.coderetard.com/2009/02/17/compare-directories-and-file-content-in-linux-without-dircmp/

Gotchas

svn:entry fields aren’t directly copied, but seem to match. repo uuid is different, but not relevant, so that file is ignored. other differences may also exist if the in_repo is an older format.

Parameters

old_dir<String>

FS Path to original (in) repository.

new_dir<String>

FS Path to generated (out) repository.

Note that these are filesystem paths, not Subversion URI’s

Returns

True, False

Whether the directories are the same (except db/uuid file) If False, puts the result of running the diff command.



35
36
37
38
39
40
41
42
43
# File 'lib/svn-transform.rb', line 35

def compare(old_dir, new_dir)
  ret = `diff --brief --exclude=uuid -r "#{old_dir}" "#{new_dir}"`
  if ret.empty?
    return true
  else
    puts ret
    return false
  end
end

Instance Method Details

#changesetsObject

Process the existing changesets and generate a SvnFixture::Revision for each.

TODO This is a massive mess. It works, at least for my purposes. But it is a mess. Ideally, it should be multiple methods. Part of this is due to how I set up the SvnFixture::Revision class, which accepts a block at initialize that is process only when its #commit method is called.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/svn-transform.rb', line 202

def changesets
  args = ['', 1, @in_repo.latest_revnum, 0, true, nil]
  path_renames = {}
  
  @in_repo.log(*args) do |changes, rev_num, author, date, msg|
    print "#{rev_num} " if PRINT_INFO
    # Sort so that files are processed first (for benefit of PropsToYaml),
    # and deletes are last
    changes = changes.sort { |a,b| sort_for(a, rev_num) <=> sort_for(b, rev_num) }
    # Get revision properties
    rev_props = @ctx.revprop_list(@in_repo_uri, rev_num)[0]
    # Create Revision, including all revprops. Note that svn:author and 
    # svn:date are revprops. SvnFixture::Revision allows these without the
    # svn: prefix (as Symbol), but revprops are written last, and so this
    # should be completely accurate.
    in_repo = @in_repo
    out_wc_path = @out_repo.wc_path
    svn_transform = self
    @out_repo.revision(rev_num, msg, rev_props) do
      print "#{rev_num} " if SvnTransform::PRINT_INFO
      # Now go through all the changes. Setup directorie structure for each
      # node. This is easier to understand, in my opinion.
      
      changes.each do |full_path, change|
        full_path = Pathname.new(full_path.sub(/\A\//, ''))
        # Descend to parent directory
        parent_dir = self
        full_path.dirname.descend do |path|
          unless path.basename == '.'
            parent_dir = parent_dir.dir(path.basename.to_s)
          end
        end
        
        # TODO Replaces
        
        copy_from_path = nil
        if change.copyfrom_path
          short_from_path = path_renames[change.copyfrom_path] || change.copyfrom_path
          copy_from_path = ::File.join(out_wc_path, short_from_path)
        end
        
        if change.action == 'D'
          del_path = path_renames['/' + full_path.to_s] || full_path.to_s
          @ctx.delete(::File.join(out_wc_path, del_path))
        elsif in_repo.stat(full_path.to_s, rev_num).file?
          data = in_repo.file(full_path.to_s, rev_num)
          transform_file = ::SvnTransform::File.new(full_path, data, rev_num, rev_props)
          original_path = transform_file.path
          svn_transform.__send__(:process_file_transforms, transform_file)
          @ctx.cp(copy_from_path, ::File.join(out_wc_path, transform_file.path.to_s)) if copy_from_path
          unless transform_file.skip?
            parent_dir.file(transform_file.basename) do
              body(transform_file.body)
              props(transform_file.properties)
            end
            # For benefit of copies
            if original_path != transform_file.path
              path_renames['/' + original_path] = '/' + transform_file.path.to_s
            end
          end
        else # directory
          # Paths don't change for directories, but use this for consistency
          @ctx.cp(copy_from_path, ::File.join(out_wc_path, full_path.to_s)) if copy_from_path
          parent_dir.dir(full_path.basename.to_s) do
            data = in_repo.dir(full_path.to_s, rev_num)
            transform_dir = ::SvnTransform::Dir.new(full_path, data, rev_num, rev_props, in_repo, self)
            svn_transform.__send__(:process_dir_transforms, transform_dir)
            props(transform_dir.properties)
          end
        end
      end
    end
  end
end

#convertObject

Run the conversion. This method sets up the connection to the existing repo and the SvnFixture that will generate the final transformed repo, then calls changesets to do the actual work. Finally, commit the SvnFixture (out repo) and update its rev 0 date to match the in repo



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/svn-transform.rb', line 178

def convert
  in_repo_session = Session.new(@in_repo_uri, @in_username, @in_password)
  @in_repo = in_repo_session.session
  @ctx = in_repo_session.context
  @out_repo = SvnFixture.repo(@out_repo_name, @out_repos_path, @out_wc_path)
  
  # Process changesets and commit
  puts "\nReading existing log..." if PRINT_INFO
  changesets
  puts "\nCommitting to new..." if PRINT_INFO
  @out_repo.commit
  
  # Update rev 0 date
  r0_date = @ctx.revprop_list(@in_repo_uri, 0)[0]['svn:date']
  @out_repo.repos.fs.set_prop('svn:date', SvnFixture.svn_time(r0_date), 0)
end

#dir_transform(klass = nil, *args, &block) ⇒ Object

Add a transform to be run on directories. See file_transform



164
165
166
167
168
169
170
171
172
# File 'lib/svn-transform.rb', line 164

def dir_transform(klass = nil, *args, &block)
  if klass
    @dir_transforms << [klass, args]
  elsif block_given?
    @dir_transforms << block
  else
    raise(ArgumentError, "Class or Block required")
  end
end

#file_transform(klass = nil, *args, &block) ⇒ Object

Add a transform to be run on files. This can either be a class or a block (see Parameters). Each file at revision is given as an SvnTransform::File to each transform, which can alter the basename, body and/or properties of the file prior to its being committed to the new Repository.

Parameters

klass<Class>

A class whose #initialize method accepts a SvnTransform::File as the first argument and which responds to #run.

args<Array>

Additional arguments to pass to klass#initialize

block<Proc>

A block that accepts one argument (a SvnTransform::File). If a klass is also given, the block is ignored

Returns

Array

The current @file_transforms Array

Raises

ArgumentError

Neither a Class nor a block was given.



153
154
155
156
157
158
159
160
161
# File 'lib/svn-transform.rb', line 153

def file_transform(klass = nil, *args, &block)
  if klass
    @file_transforms << [klass, args]
  elsif block_given?
    @file_transforms << block
  else
    raise(ArgumentError, "Class or Block required")
  end
end