Module: SvnCommandHelper::Svn

Extended by:
SystemCommandHelper
Defined in:
lib/svn_command_helper.rb

Overview

Subversion native command and some utilities

Class Method Summary collapse

Class Method Details

.base_uri_of(uris) ⇒ String

find common part of the given uris

Parameters:

  • uris (Array<uri string like>)

    target uri

Returns:

  • (String)

    common part of the given uris



259
260
261
262
263
264
265
# File 'lib/svn_command_helper.rb', line 259

def base_uri_of(uris)
  uris.reduce(Pathname.new(uris.first.to_s)) do |base_uri, uri|
    rel = Pathname.new(uri).relative_path_from(base_uri)
    to_parent = rel.to_s.match(/(?:\.\.\/)*/).to_s
    to_parent.empty? ? base_uri : base_uri + to_parent
  end.to_s
end

.cat(path) ⇒ String

svn cat

Parameters:

  • path (path string like)

    target path

Returns:

  • (String)

    file contents



245
246
247
# File 'lib/svn_command_helper.rb', line 245

def cat(path)
  cap("svn cat #{path}")
end

.check_exists(transaction, raise_if_from_not_found = true) ⇒ Boolean

check transaction from file exists

Parameters:

  • transaction (SvnFileCopyTransaction)

    from and to info

  • raise_if_from_not_found (Boolean) (defaults to: true)

    raise if from not found

Returns:

  • (Boolean)

    true if file exists



397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/svn_command_helper.rb', line 397

def check_exists(transaction, raise_if_from_not_found = true)
  unless transaction.from_exist?
    if !raise_if_from_not_found
      false
    elsif transaction.to_exist?
      puts "[WARNING] File:#{file}はコピー先のみにあります"
      false
    else
      raise "[Error] File:#{file}が見つかりません!"
    end
  else
    true
  end
end

.commit(message, path = ".") ⇒ Object

svn commit

Parameters:

  • message (String)

    commit message

  • path (path string like) (defaults to: ".")

    target path



21
22
23
24
25
26
27
28
29
# File 'lib/svn_command_helper.rb', line 21

def commit(message, path = ".")
  if cap("svn status #{path}").empty?
    sys "svn revert -R #{path}"
    puts "[WARNING] no change: #{message}"
  else
    sys "svn commit -m '#{message}' #{path}"
  end
  sys "svn update #{path}"
end

.copied_revision(uri = ".") ⇒ Object

stop-on-copy revision of uri return [Integer] revision number

Parameters:

  • uri (uri string like) (defaults to: ".")

    target uri



134
135
136
# File 'lib/svn_command_helper.rb', line 134

def copied_revision(uri = ".")
  log(uri, stop_on_copy: true).first.revision
end

.copy_multi(transactions, message) ⇒ Object

copy multi transactions

Parameters:



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/svn_command_helper.rb', line 364

def copy_multi(transactions, message)
  base_uri = base_uri_of(transactions.map(&:from_base) + transactions.map(&:to_base))
  transactions.each do |transaction|
    raise "copy_multi: #{transaction.from} not exists" unless transaction.from_exist?
  end
  Dir.mktmpdir do |dir|
    Dir.chdir(dir) do
      sys "svn checkout --depth empty #{base_uri} ."
      root = Svn.working_copy_root_path(".")
      transactions.each do |transaction|
        relative_to = transaction.relative_to(base_uri)

        if transaction.to_exist?  # toがある場合マージ
          Svn.update_deep(relative_to, "empty", false, root: root)
          begin
            Svn.merge1(1, "HEAD", transaction.from, relative_to, "--accept theirs-full")
          rescue
            sys "svn export --force #{transaction.from} #{relative_to}"
          end
        else # toがない場合コピー
          Svn.update_deep(File.dirname(relative_to), "empty", false, root: root) # mkpath的な なくてもエラーにはならないので
          sys "svn copy --parents #{transaction.from} #{relative_to}"
        end
      end
      Svn.commit(message, ".")
    end
  end
end

.copy_single(transaction, message, recursive = false) ⇒ Object

copy single transaction

Parameters:

  • transaction (SvnFileCopyTransaction)

    from and to info

  • message (String)

    commit message

  • recursive (Boolean) (defaults to: false)

    list –recursive



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/svn_command_helper.rb', line 330

def copy_single(transaction, message, recursive = false)
  transactions = transaction.glob_transactions(recursive)
  raise "copy_single: #{transaction.from} not exists" if transactions.empty?
  to_exist_transactions = Svn.list_files(transaction.to_base).map do |entry|
    transactions.find {|_transaction| _transaction.file == entry.path}
  end.compact
  only_from_transactions = transactions - to_exist_transactions
  if to_exist_transactions.empty? # toにファイルがない
    sys "svn copy --parents #{only_from_transactions.map(&:from).join(' ')} #{transaction.to_base} -m '#{message}'"
  else
    Dir.mktmpdir do |dir|
      Dir.chdir(dir) do
        sys "svn checkout --depth empty #{transaction.to_base} ."
        # なくてもエラーにならないので全部update
        sys "svn update --set-depth infinity #{transactions.map(&:file).join(' ')}"
        unless only_from_transactions.empty?
          sys "svn copy --parents #{only_from_transactions.map(&:from).join(' ')} ."
        end
        to_exist_transactions.each do |_transaction|
          begin
            Svn.merge1(1, "HEAD", _transaction.from, _transaction.file, "--accept theirs-full")
          rescue
            sys "svn export --force #{_transaction.from} #{_transaction.file}"
          end
        end
        Svn.commit(message, ".")
      end
    end
  end
end

.diff(from_uri, to_uri, ignore_properties: false, ignore_eol_style: false, ignore_space_change: false, ignore_all_space: false) ⇒ String

svn diff

Parameters:

  • from_uri (String)

    from uri

  • to_uri (String)

    to uri

  • ignore_properties (Boolean) (defaults to: false)

    –ignore-properties

  • ignore_eol_style (Boolean) (defaults to: false)

    -x –ignore-eol-style

  • ignore_space_change (Boolean) (defaults to: false)

    -x –ignore-space-change

  • ignore_all_space (Boolean) (defaults to: false)

    -x –ignore-all-space

Returns:

  • (String)

    raw diff str



275
276
277
278
279
280
281
282
# File 'lib/svn_command_helper.rb', line 275

def diff(from_uri, to_uri, ignore_properties: false, ignore_eol_style: false, ignore_space_change: false, ignore_all_space: false)
  options = []
  options << "-x --ignore-eol-style" if ignore_eol_style
  options << "-x --ignore-space-change" if ignore_space_change
  options << "-x --ignore-all-space" if ignore_all_space
  options << "--ignore-properties" if ignore_properties
  cap("svn diff #{from_uri} #{to_uri} #{options.join(' ')}")
end

.exist?(uri) ⇒ Boolean

check svn uri exists or not

Parameters:

  • uri (uri string like)

    target uri

Returns:

  • (Boolean)

    true if exists



87
88
89
90
# File 'lib/svn_command_helper.rb', line 87

def exist?(uri)
  basename = File.basename(uri)
  !list(File.dirname(uri)).find{|entry| File.fnmatch(basename, entry.path)}.nil?
end

.exist_file?(uri) ⇒ Boolean

check svn uri file exists or not

Parameters:

  • uri (uri string like)

    target uri

Returns:

  • (Boolean)

    true if exists



95
96
97
98
# File 'lib/svn_command_helper.rb', line 95

def exist_file?(uri)
  file = File.basename(uri)
  !list_files(File.dirname(uri)).find{|entry| File.fnmatch(file, entry.path)}.nil?
end

.info(path = ".") ⇒ Hash<String, String>

svn info -> yaml parse

Parameters:

  • path (path string like) (defaults to: ".")

    target path

Returns:

  • (Hash<String, String>)

    svn info contents



238
239
240
# File 'lib/svn_command_helper.rb', line 238

def info(path = ".")
  YAML.load(cap("svn info #{path}"))
end

.list(uri, recursive = false) ⇒ Array<String>

svn list

Parameters:

  • uri (uri string like)

    target uri

  • recursive (Boolean) (defaults to: false)

    –recursive

Returns:

  • (Array<String>)

    paths



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/svn_command_helper.rb', line 35

def list(uri, recursive = false)
  if @list_cache && @list_cache[recursive][uri]
    @list_cache[recursive][uri]
  else
    list_str = cap("svn list --xml #{recursive ? '-R' : ''} #{uri}")
    list = REXML::Document.new(list_str).elements.collect("/lists/list/entry") do |entry|
      commit = entry.elements["commit"]
      OpenStruct.new({
        kind: entry.attribute("kind").value,
        path: entry.elements["name"].text,
        revision: commit.attribute("revision").value.to_i,
        author: commit.elements["author"].text,
        date: Time.iso8601(commit.elements["date"].text),
      })
    end
    @list_cache[recursive][uri] = list if @list_cache
    list
  end
end

.list_cache(&block) ⇒ Object

svn list cache block



78
79
80
81
82
# File 'lib/svn_command_helper.rb', line 78

def list_cache(&block)
  @list_cache = {true => {}, false => {}}
  block.call
  @list_cache = nil
end

.list_files(uri, recursive = false) ⇒ Array<String>

svn list -> grep only files

Parameters:

  • uri (uri string like)

    target uri

  • recursive (Boolean) (defaults to: false)

    –recursive

Returns:

  • (Array<String>)

    file paths



66
67
68
# File 'lib/svn_command_helper.rb', line 66

def list_files(uri, recursive = false)
  list(uri, recursive).select {|entry| entry.kind == "file"}
end

.list_files_recursive(uri) ⇒ Array<String>

svn list –recursive -> grep only files

Parameters:

  • uri (uri string like)

    target uri

Returns:

  • (Array<String>)

    file paths



73
74
75
# File 'lib/svn_command_helper.rb', line 73

def list_files_recursive(uri)
  list_files(uri, true)
end

.list_recursive(uri) ⇒ Array<String>

svn list –recursive

Parameters:

  • uri (uri string like)

    target uri

Returns:

  • (Array<String>)

    paths



58
59
60
# File 'lib/svn_command_helper.rb', line 58

def list_recursive(uri)
  list(uri, true)
end

.log(uri = ".", limit: nil, stop_on_copy: false) ⇒ Array<OpenStruct>

svn log

Parameters:

  • uri (uri string like) (defaults to: ".")

    target uri

  • limit (Integer) (defaults to: nil)

    –limit

  • stop_on_copy (Boolean) (defaults to: false)

    –stop-on-copy

Returns:

  • (Array<OpenStruct>)

    log (old to new order)



112
113
114
115
116
117
118
119
120
121
122
# File 'lib/svn_command_helper.rb', line 112

def log(uri = ".", limit: nil, stop_on_copy: false)
  log = cap "svn log --xml #{limit ? "--limit #{limit}" : ""} #{stop_on_copy ? "--stop-on-copy" : ""} #{uri}"
  REXML::Document.new(log).elements.collect("/log/logentry") do |entry|
    OpenStruct.new({
      revision: entry.attribute("revision").value.to_i,
      author: entry.elements["author"].text,
      date: Time.iso8601(entry.elements["date"].text),
      msg: entry.elements["msg"].text,
    })
  end.reverse
end

.merge1(start_rev, end_rev, from_uri, to_path = ".", extra = "") ⇒ Object

svn merge -r start_rev:end_rev from_uri to_path

Parameters:

  • start_rev (Integer)

    start revision

  • end_rev (Integer)

    end revision

  • from_uri (String)

    from uri

  • to_path (String) (defaults to: ".")

    to local path

  • extra (String) (defaults to: "")

    extra options



169
170
171
# File 'lib/svn_command_helper.rb', line 169

def merge1(start_rev, end_rev, from_uri, to_path = ".", extra = "")
  safe_merge merge1_command(start_rev, end_rev, from_uri, to_path, extra)
end

.merge1_command(start_rev, end_rev, from_uri, to_path = ".", extra = "", dry_run: false) ⇒ Object

“svn merge -r start_rev:end_rev from_uri to_path”

Parameters:

  • start_rev (Integer)

    start revision

  • end_rev (Integer)

    end revision

  • from_uri (String)

    from uri

  • to_path (String) (defaults to: ".")

    to local path

  • extra (String) (defaults to: "")

    extra options

  • dry_run (Boolean) (defaults to: false)

    –dry-run



190
191
192
# File 'lib/svn_command_helper.rb', line 190

def merge1_command(start_rev, end_rev, from_uri, to_path = ".", extra = "", dry_run: false)
  "svn merge -r #{start_rev}:#{end_rev} #{from_uri} #{to_path} #{extra} #{dry_run ? "--dry-run" : ""}"
end

.merge1_dry_run(start_rev, end_rev, from_uri, to_path = ".", extra = "") ⇒ Object

svn merge -r start_rev:end_rev from_uri to_path –dry-run

Parameters:

  • start_rev (Integer)

    start revision

  • end_rev (Integer)

    end revision

  • from_uri (String)

    from uri

  • to_path (String) (defaults to: ".")

    to local path

  • extra (String) (defaults to: "")

    extra options



179
180
181
# File 'lib/svn_command_helper.rb', line 179

def merge1_dry_run(start_rev, end_rev, from_uri, to_path = ".", extra = "")
  merge_dry_run merge1_command(start_rev, end_rev, from_uri, to_path, extra)
end

.merge_branch_to_trunk(from_uri, to_path = ".") ⇒ Object

svn merge branch to trunk with detecting revision range

Parameters:

  • from_uri (String)

    from uri

  • to_path (String) (defaults to: ".")

    to local path



217
218
219
220
221
# File 'lib/svn_command_helper.rb', line 217

def merge_branch_to_trunk(from_uri, to_path = ".")
  start_rev = copied_revision(from_uri)
  end_rev = revision(from_uri)
  merge1(start_rev, end_rev, from_uri, to_path)
end

.merge_dry_run(command) ⇒ Object

merge dry-run conflict check result

Parameters:

  • command (String)

    svn merge full command



208
209
210
211
212
# File 'lib/svn_command_helper.rb', line 208

def merge_dry_run(command)
  cap("#{command} --dry-run")
    .each_line.map(&:chomp).reject {|line| line[4] != " "}
    .map {|line| OpenStruct.new({status: line[0...4], path: line[5..-1]})}
end

.reverse_merge(start_rev, end_rev = nil, path = ".") ⇒ Object

reverse merge single revision

Parameters:

  • start_rev (Integer)

    start revision

  • end_rev (Integer) (defaults to: nil)

    end revision (if no end_rev then “-c start_rev”)

  • path (String) (defaults to: ".")

    local path



227
228
229
230
231
232
233
# File 'lib/svn_command_helper.rb', line 227

def reverse_merge(start_rev, end_rev = nil, path = ".")
  if end_rev
    safe_merge "svn merge -r #{end_rev}:#{start_rev} #{path}"
  else
    safe_merge "svn merge -c #{start_rev} #{path}"
  end
end

.revision(uri = ".") ⇒ Object

head revision of uri return [Integer] revision number

Parameters:

  • uri (uri string like) (defaults to: ".")

    target uri



127
128
129
# File 'lib/svn_command_helper.rb', line 127

def revision(uri = ".")
  log(uri, limit: 1).last.revision
end

.safe_merge(command) ⇒ Object

merge after dry-run conflict check

Parameters:

  • command (String)

    svn merge full command



196
197
198
199
200
201
202
203
204
# File 'lib/svn_command_helper.rb', line 196

def safe_merge(command)
  dry_run = merge_dry_run(command)
  if dry_run.any? {|entry| entry.status.include?("C")}
    dry_run_str = dry_run.map {|entry| "#{entry.status} #{entry.path}"}.join("\n")
    raise "[ERROR] merge_branch_to_trunk: `#{command}` has conflict!\n#{dry_run_str}"
  else
    sys command
  end
end

.summarize_diff(from_uri, to_uri, ignore_properties: false, ignore_eol_style: false, ignore_space_change: false, ignore_all_space: false, with_list_info: false) ⇒ Array

svn diff –summarize

Parameters:

  • from_uri (String)

    from uri

  • to_uri (String)

    to uri

  • ignore_properties (Boolean) (defaults to: false)

    | grep -v ‘^ ’

  • ignore_eol_style (Boolean) (defaults to: false)

    -x –ignore-eol-style

  • ignore_space_change (Boolean) (defaults to: false)

    -x –ignore-space-change

  • ignore_all_space (Boolean) (defaults to: false)

    -x –ignore-all-space

  • with_list_info (Boolean) (defaults to: false)

    with svn list info

Returns:

  • (Array)

    diff files list



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/svn_command_helper.rb', line 293

def summarize_diff(from_uri, to_uri, ignore_properties: false, ignore_eol_style: false, ignore_space_change: false, ignore_all_space: false, with_list_info: false)
  options = []
  options << "-x --ignore-eol-style" if ignore_eol_style
  options << "-x --ignore-space-change" if ignore_space_change
  options << "-x --ignore-all-space" if ignore_all_space

  diff_str = cap("svn diff --xml --summarize #{from_uri} #{to_uri} #{options.join(' ')}")
  diff_list = REXML::Document.new(diff_str).elements.collect("/diff/paths/path") do |path|
    OpenStruct.new({
      kind: path.attribute("kind").value,
      item: path.attribute("item").value,
      props: path.attribute("props").value,
      path: path.text,
    })
  end
  if ignore_properties
    diff_list.reject! {|diff| diff.item == "none"}
  end
  if with_list_info
    from_entries = Svn.list_recursive(from_uri)
    to_entries = Svn.list_recursive(to_uri)
    from_entries_hash = from_entries.each.with_object({}) {|file, entries_hash| entries_hash[file.path] = file}
    to_entries_hash = to_entries.each.with_object({}) {|file, entries_hash| entries_hash[file.path] = file}
    from_uri_path = Pathname.new(from_uri)
    diff_list.each do |diff|
      path = Pathname.new(diff.path).relative_path_from(from_uri_path).to_s
      diff.from = from_entries_hash[path]
      diff.to = to_entries_hash[path]
    end
  end
  diff_list
end

.update(path = ".", depth = nil) ⇒ Object

svn update

Parameters:

  • path (path string like) (defaults to: ".")

    target path

  • depth (depth) (defaults to: nil)

    –set-depth



103
104
105
# File 'lib/svn_command_helper.rb', line 103

def update(path = ".", depth = nil)
  sys "svn update #{depth ? "--set-depth #{depth}" : ""} #{path}"
end

.update_deep(path, depth = nil, exist_path_update = true, root: nil) ⇒ Object

svn update to deep path recursive

Parameters:

  • path (path string like)

    target path

  • depth (Integer) (defaults to: nil)

    –set-depth for only new updated dirs

  • exist_path_update (Boolean) (defaults to: true)

    middle path update flag

  • root (String) (defaults to: nil)

    working copy root path



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/svn_command_helper.rb', line 143

def update_deep(path, depth = nil, exist_path_update = true, root: nil)
  exist_path = path
  until File.exist?(exist_path)
    exist_path = File.dirname(exist_path)
  end
  root = Pathname.new(root || Svn.working_copy_root_path(exist_path))
  end_path = Pathname.new(path.to_s).expand_path
  parents = [end_path]
  while parents.first != root
    parents.unshift(parents.first.parent)
  end
  parents.each do |dir|
    if dir.exist?
      sys "svn update #{dir}" if exist_path_update
    else
      sys "svn update #{depth ? "--set-depth #{depth}" : ""} #{dir}"
    end
  end
end

.working_copy_root_path(path = ".") ⇒ String

Working Copy Root Path from svn info

Parameters:

  • path (path string like) (defaults to: ".")

    target path

Returns:

  • (String)

    Working Copy Root Path



252
253
254
# File 'lib/svn_command_helper.rb', line 252

def working_copy_root_path(path = ".")
  info(path)["Working Copy Root Path"]
end