Module: SvnCommandHelper::Svn

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

Overview

Subversion native command and some utilities

Defined Under Namespace

Classes: DiffItem, ListItem, LogItem, MergeStatusItem

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



287
288
289
290
291
292
293
# File 'lib/svn_command_helper.rb', line 287

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



273
274
275
# File 'lib/svn_command_helper.rb', line 273

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



436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/svn_command_helper.rb', line 436

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



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

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



154
155
156
# File 'lib/svn_command_helper.rb', line 154

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

.copy_multi(transactions, message) ⇒ Object

copy multi transactions

Parameters:



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/svn_command_helper.rb', line 403

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



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/svn_command_helper.rb', line 369

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



303
304
305
306
307
308
309
310
# File 'lib/svn_command_helper.rb', line 303

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



97
98
99
100
# File 'lib/svn_command_helper.rb', line 97

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



105
106
107
108
# File 'lib/svn_command_helper.rb', line 105

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



266
267
268
# File 'lib/svn_command_helper.rb', line 266

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

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

svn list

Parameters:

  • uri (uri string like)

    target uri

  • recursive (Boolean) (defaults to: false)

    –recursive

Returns:



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

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 = LibXML::XML::Document.string(list_str).find("//lists/list/entry").map do |entry|
      commit = entry.find_first("commit")
      ListItem.new(
        kind: entry["kind"],
        path: entry.find_first("name").content,
        revision: commit["revision"].to_i,
        author: commit.find_first("author").content,
        date: Time.iso8601(commit.find_first("date").content)
      )
    end
    @list_cache[recursive][uri] = list if @list_cache
    list
  end
end

.list_cache(&block) ⇒ Object

svn list cache block



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

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



76
77
78
# File 'lib/svn_command_helper.rb', line 76

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



83
84
85
# File 'lib/svn_command_helper.rb', line 83

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



68
69
70
# File 'lib/svn_command_helper.rb', line 68

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

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

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<LogItem>)

    log (old to new order)



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/svn_command_helper.rb', line 122

def log(uri = ".", limit: nil, stop_on_copy: false)
  log = cap "svn log --xml #{limit ? "--limit #{limit}" : ""} #{stop_on_copy ? "--stop-on-copy" : ""} #{uri}"
  LibXML::XML::Document.string(log).find("//log/logentry").map do |entry|
    LogItem.new(
      revision: entry["revision"].to_i,
      author: entry.find_first("author").content,
      date: Time.iso8601(entry.find_first("date").content),
      msg: entry.find_first("msg").content
    )
  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



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

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



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

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



199
200
201
# File 'lib/svn_command_helper.rb', line 199

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



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

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



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

def merge_dry_run(command)
  cap("#{command} --dry-run")
    .each_line.map(&:chomp).reject {|line| line[4] != " "}
    .map {|line| MergeStatusItem.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



255
256
257
258
259
260
261
# File 'lib/svn_command_helper.rb', line 255

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



147
148
149
# File 'lib/svn_command_helper.rb', line 147

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



216
217
218
219
220
221
222
223
224
# File 'lib/svn_command_helper.rb', line 216

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



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/svn_command_helper.rb', line 321

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 = LibXML::XML::Document.string(diff_str).find("//diff/paths/path").map do |path|
    DiffItem.new(
      kind: path["kind"],
      item: path["item"],
      props: path["props"],
      path: path.content
    )
  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



113
114
115
# File 'lib/svn_command_helper.rb', line 113

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/svn_command_helper.rb', line 163

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



280
281
282
# File 'lib/svn_command_helper.rb', line 280

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