pdk 1.14.1 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/lib/pdk/answer_file.rb +5 -7
  4. data/lib/pdk/cli.rb +1 -0
  5. data/lib/pdk/cli/console.rb +1 -1
  6. data/lib/pdk/cli/convert.rb +10 -2
  7. data/lib/pdk/cli/exec.rb +2 -1
  8. data/lib/pdk/cli/module/build.rb +1 -1
  9. data/lib/pdk/cli/module/generate.rb +1 -1
  10. data/lib/pdk/cli/release.rb +192 -0
  11. data/lib/pdk/cli/release/prep.rb +39 -0
  12. data/lib/pdk/cli/release/publish.rb +40 -0
  13. data/lib/pdk/cli/update.rb +12 -0
  14. data/lib/pdk/config.rb +1 -1
  15. data/lib/pdk/config/namespace.rb +1 -1
  16. data/lib/pdk/generate/module.rb +11 -17
  17. data/lib/pdk/generate/puppet_object.rb +1 -2
  18. data/lib/pdk/generate/task.rb +1 -1
  19. data/lib/pdk/module.rb +2 -1
  20. data/lib/pdk/module/build.rb +15 -25
  21. data/lib/pdk/module/convert.rb +4 -9
  22. data/lib/pdk/module/metadata.rb +1 -3
  23. data/lib/pdk/module/release.rb +260 -0
  24. data/lib/pdk/module/template_dir.rb +115 -0
  25. data/lib/pdk/module/template_dir/base.rb +268 -0
  26. data/lib/pdk/module/template_dir/git.rb +91 -0
  27. data/lib/pdk/module/template_dir/local.rb +21 -0
  28. data/lib/pdk/module/update.rb +17 -5
  29. data/lib/pdk/module/update_manager.rb +1 -1
  30. data/lib/pdk/report.rb +18 -12
  31. data/lib/pdk/report/event.rb +6 -3
  32. data/lib/pdk/template_file.rb +2 -2
  33. data/lib/pdk/util.rb +17 -6
  34. data/lib/pdk/util/bundler.rb +8 -9
  35. data/lib/pdk/util/changelog_generator.rb +115 -0
  36. data/lib/pdk/util/filesystem.rb +62 -2
  37. data/lib/pdk/util/git.rb +60 -8
  38. data/lib/pdk/util/puppet_version.rb +4 -5
  39. data/lib/pdk/util/ruby_version.rb +3 -3
  40. data/lib/pdk/util/template_uri.rb +49 -40
  41. data/lib/pdk/util/version.rb +4 -4
  42. data/lib/pdk/validate/metadata/metadata_syntax.rb +2 -2
  43. data/lib/pdk/validate/puppet/puppet_epp.rb +2 -4
  44. data/lib/pdk/validate/puppet/puppet_syntax.rb +2 -4
  45. data/lib/pdk/validate/tasks/metadata_lint.rb +2 -2
  46. data/lib/pdk/validate/yaml/syntax.rb +3 -3
  47. data/lib/pdk/version.rb +1 -1
  48. data/locales/pdk.pot +401 -149
  49. metadata +11 -3
  50. data/lib/pdk/module/templatedir.rb +0 -391
@@ -0,0 +1,21 @@
1
+ require 'pdk'
2
+ require 'pdk/module/template_dir/base'
3
+
4
+ module PDK
5
+ module Module
6
+ module TemplateDir
7
+ class Local < Base
8
+ def template_path(uri)
9
+ [uri.shell_path, false]
10
+ end
11
+
12
+ # For plain fileystem directories, this will return the URL to the repository only.
13
+ #
14
+ # @return [Hash{String => String}] A hash of identifying metadata.
15
+ def metadata
16
+ super.merge('template-url' => uri.bare_uri)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -6,7 +6,7 @@ module PDK
6
6
  GIT_DESCRIBE_PATTERN = %r{\A(?<base>.+?)-(?<additional_commits>\d+)-g(?<sha>.+)\Z}
7
7
 
8
8
  def run
9
- template_uri.git_ref = new_template_version
9
+ template_uri.uri_fragment = new_template_version
10
10
 
11
11
  stage_changes!
12
12
 
@@ -71,15 +71,27 @@ module PDK
71
71
  def new_template_version
72
72
  return options[:'template-ref'] if options[:'template-ref']
73
73
 
74
- if template_uri.default? && template_uri.ref_is_tag? && PDK::Util.package_install?
74
+ if template_uri.default? && PDK::Util::Git.tag?(template_uri.bare_uri, template_uri.uri_fragment) && PDK::Util.package_install?
75
75
  PDK::Util::TemplateURI.default_template_ref
76
76
  else
77
- template_uri.git_ref
77
+ template_uri.uri_fragment
78
78
  end
79
79
  end
80
80
 
81
+ def pinned_to_puppetlabs_template_tag?
82
+ return false unless template_uri.puppetlabs_template?
83
+ return false unless PDK::Util::Git.tag?(template_uri.bare_uri, template_uri.uri_fragment)
84
+ return false if latest_template?
85
+
86
+ template_uri.uri_fragment == new_template_version
87
+ end
88
+
81
89
  private
82
90
 
91
+ def latest_template?
92
+ [PDK::TEMPLATE_REF, 'master'].include?(template_uri.uri_fragment)
93
+ end
94
+
83
95
  def current_template_version
84
96
  @current_template_version ||= module_metadata.data['template-ref']
85
97
  end
@@ -101,7 +113,7 @@ module PDK
101
113
  return template_ref if template_ref == PDK::TEMPLATE_REF
102
114
 
103
115
  sha_length = GIT_DESCRIBE_PATTERN.match(current_template_version)[:sha].length - 1
104
- "#{template_ref}@#{PDK::Util::Git.ls_remote(template_uri.git_remote, template_ref)[0..sha_length]}"
116
+ "#{template_ref}@#{PDK::Util::Git.ls_remote(template_uri.bare_uri, template_ref)[0..sha_length]}"
105
117
  end
106
118
 
107
119
  def update_message
@@ -113,7 +125,7 @@ module PDK
113
125
 
114
126
  format_string % {
115
127
  module_name: module_metadata.data['name'],
116
- template_url: template_uri.git_remote,
128
+ template_url: template_uri.bare_uri,
117
129
  current_version: current_version,
118
130
  new_version: new_version,
119
131
  }
@@ -186,7 +186,7 @@ module PDK
186
186
 
187
187
  require 'diff/lcs/hunk'
188
188
 
189
- file_mtime = File.stat(path).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
189
+ file_mtime = PDK::Util::Filesystem.stat(path).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
190
190
  now = Time.now.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
191
191
 
192
192
  output << "--- #{path}\t#{file_mtime}"
@@ -51,9 +51,6 @@ module PDK
51
51
  require 'time'
52
52
  require 'socket'
53
53
 
54
- # Open a File Object for IO if target is a string containing a filename or path
55
- target = File.open(target, 'w') if target.is_a? String
56
-
57
54
  document = REXML::Document.new
58
55
  document << REXML::XMLDecl.new
59
56
  testsuites = REXML::Element.new('testsuites')
@@ -81,9 +78,14 @@ module PDK
81
78
  end
82
79
 
83
80
  document.elements << testsuites
84
- document.write(target, 2)
85
- ensure
86
- target.close if target.is_a? File
81
+ report = ''
82
+ document.write(report, 2)
83
+
84
+ if target.is_a?(String)
85
+ PDK::Util::Filesystem.write_file(target, report)
86
+ else
87
+ target << report
88
+ end
87
89
  end
88
90
 
89
91
  # Renders the report as plain text.
@@ -94,22 +96,26 @@ module PDK
94
96
  # @param target [#write] an IO object that the report will be written to.
95
97
  # Defaults to PDK::Report.default_target.
96
98
  def write_text(target = self.class.default_target)
97
- # Open a File Object for IO if target is a string containing a filename or path
98
- target = File.open(target, 'w') if target.is_a? String
99
99
  coverage_report = nil
100
+ report = []
100
101
 
101
102
  events.each do |_tool, tool_events|
102
103
  tool_events.each do |event|
103
104
  if event.rspec_puppet_coverage?
104
105
  coverage_report = event.to_text
105
106
  else
106
- target.puts(event.to_text) unless event.pass?
107
+ report << event.to_text unless event.pass?
107
108
  end
108
109
  end
109
110
  end
110
- ensure
111
- target.puts "\n#{coverage_report}" if coverage_report
112
- target.close if target.is_a? File
111
+
112
+ report << "\n#{coverage_report}" if coverage_report
113
+
114
+ if target.is_a?(String)
115
+ PDK::Util::Filesystem.write_file(target, report.join("\n"))
116
+ else
117
+ target << report.join("\n")
118
+ end
113
119
  end
114
120
  end
115
121
  end
@@ -98,7 +98,7 @@ module PDK
98
98
  # results.
99
99
  def rspec_puppet_coverage?
100
100
  @rspec_puppet_coverage_pattern ||= File.join('**', 'lib', 'rspec-puppet', 'coverage.rb')
101
- source == 'rspec' && File.fnmatch?(@rspec_puppet_coverage_pattern, File.expand_path(file))
101
+ source == 'rspec' && PDK::Util::Filesystem.fnmatch?(@rspec_puppet_coverage_pattern, PDK::Util::Filesystem.expand_path(file))
102
102
  end
103
103
 
104
104
  # Renders the event in a clang style text format.
@@ -346,10 +346,13 @@ module PDK
346
346
  def context_lines(max_num_lines = 5)
347
347
  return if file.nil? || line.nil?
348
348
 
349
- file_path = [file, File.join(PDK::Util.module_root, file)].find { |r| File.file?(r) }
349
+ file_path = [file, File.join(PDK::Util.module_root, file)].find do |path|
350
+ PDK::Util::Filesystem.file?(path)
351
+ end
352
+
350
353
  return if file_path.nil?
351
354
 
352
- file_content = File.read(file_path).split("\n")
355
+ file_content = PDK::Util::Filesystem.read_file(file_path).split("\n")
353
356
  delta = (max_num_lines - 1) / 2
354
357
  min = [0, (line - 1) - delta].max
355
358
  max = [(line - 1) + delta, file_content.length].min
@@ -62,8 +62,8 @@ module PDK
62
62
  #
63
63
  # @api private
64
64
  def template_content
65
- if File.file?(@template_file) && File.readable?(@template_file)
66
- return File.read(@template_file)
65
+ if PDK::Util::Filesystem.file?(@template_file) && PDK::Util::Filesystem.readable?(@template_file)
66
+ return PDK::Util::Filesystem.read_file(@template_file)
67
67
  end
68
68
 
69
69
  raise ArgumentError, _("'%{template}' is not a readable file") % { template: @template_file }
@@ -10,6 +10,7 @@ autoload :Pathname, 'pathname'
10
10
  module PDK
11
11
  module Util
12
12
  autoload :Bundler, 'pdk/util/bundler'
13
+ autoload :ChangelogGenerator, 'pdk/util/changelog_generator'
13
14
  autoload :Env, 'pdk/util/env'
14
15
  autoload :Filesystem, 'pdk/util/filesystem'
15
16
  autoload :Git, 'pdk/util/git'
@@ -31,6 +32,16 @@ module PDK
31
32
  types
32
33
  ].freeze
33
34
 
35
+ #:nocov:
36
+ # This method just wraps core Ruby functionality and
37
+ # can be ignored for code coverage
38
+
39
+ # Calls Kernel.exit with an exitcode
40
+ def exit_process(exit_code)
41
+ exit exit_code
42
+ end
43
+ #:nocov:
44
+
34
45
  # Searches upwards from current working directory for the given target file.
35
46
  #
36
47
  # @param target [String] Name of file to search for.
@@ -40,13 +51,13 @@ module PDK
40
51
  # nil if the target file could not be found.
41
52
  def find_upwards(target, start_dir = nil)
42
53
  previous = nil
43
- current = File.expand_path(start_dir || Dir.pwd)
54
+ current = PDK::Util::Filesystem.expand_path(start_dir || Dir.pwd)
44
55
 
45
- until !File.directory?(current) || current == previous
56
+ until !PDK::Util::Filesystem.directory?(current) || current == previous
46
57
  filename = File.join(current, target)
47
- return filename if File.file?(filename)
58
+ return filename if PDK::Util::Filesystem.file?(filename)
48
59
  previous = current
49
- current = File.expand_path('..', current)
60
+ current = PDK::Util::Filesystem.expand_path('..', current)
50
61
  end
51
62
  end
52
63
  module_function :find_upwards
@@ -72,12 +83,12 @@ module PDK
72
83
  # @return [String] Canonical path
73
84
  def canonical_path(path)
74
85
  if Gem.win_platform?
75
- unless File.exist?(path)
86
+ unless PDK::Util::Filesystem.exist?(path)
76
87
  raise PDK::CLI::FatalError, _("Cannot resolve a full path to '%{path}', as it does not currently exist.") % { path: path }
77
88
  end
78
89
  PDK::Util::Windows::File.get_long_pathname(path)
79
90
  else
80
- File.expand_path(path)
91
+ PDK::Util::Filesystem.expand_path(path)
81
92
  end
82
93
  end
83
94
  module_function :canonical_path
@@ -6,8 +6,6 @@ module PDK
6
6
  class BundleHelper; end
7
7
 
8
8
  def self.ensure_bundle!(gem_overrides = nil)
9
- require 'fileutils'
10
-
11
9
  bundle = BundleHelper.new
12
10
 
13
11
  # This will default ensure_bundle! to re-resolving everything to latest
@@ -34,11 +32,11 @@ module PDK
34
32
  original_lockfile = bundle.gemfile_lock
35
33
  temp_lockfile = "#{original_lockfile}.tmp"
36
34
 
37
- FileUtils.mv(original_lockfile, temp_lockfile)
35
+ PDK::Util::Filesystem.mv(original_lockfile, temp_lockfile)
38
36
 
39
37
  all_deps_available = bundle.installed?(gem_overrides)
40
38
  ensure
41
- FileUtils.mv(temp_lockfile, original_lockfile, force: true)
39
+ PDK::Util::Filesystem.mv(temp_lockfile, original_lockfile, force: true)
42
40
  end
43
41
 
44
42
  bundle.update_lock!(with: gem_overrides, local: all_deps_available)
@@ -91,7 +89,7 @@ module PDK
91
89
  end
92
90
 
93
91
  def locked?
94
- !gemfile_lock.nil? && File.file?(gemfile_lock)
92
+ !gemfile_lock.nil? && PDK::Util::Filesystem.file?(gemfile_lock)
95
93
  end
96
94
 
97
95
  def installed?(gem_overrides = {})
@@ -110,7 +108,6 @@ module PDK
110
108
 
111
109
  def lock!
112
110
  require 'pdk/util'
113
- require 'fileutils'
114
111
  require 'pdk/util/ruby_version'
115
112
 
116
113
  if PDK::Util.package_install?
@@ -121,7 +118,9 @@ module PDK
121
118
  File.join(PDK::Util.package_cachedir, 'Gemfile.lock'),
122
119
  ]
123
120
 
124
- vendored_gemfile_lock = vendored_lockfiles.find { |lockfile| File.exist?(lockfile) }
121
+ vendored_gemfile_lock = vendored_lockfiles.find do |lockfile|
122
+ PDK::Util::Filesystem.exist?(lockfile)
123
+ end
125
124
 
126
125
  unless vendored_gemfile_lock
127
126
  raise PDK::CLI::FatalError, _('Vendored Gemfile.lock (%{source}) not found.') % {
@@ -130,7 +129,7 @@ module PDK
130
129
  end
131
130
 
132
131
  PDK.logger.debug(_('Using vendored Gemfile.lock from %{source}.') % { source: vendored_gemfile_lock })
133
- FileUtils.cp(vendored_gemfile_lock, File.join(PDK::Util.module_root, 'Gemfile.lock'))
132
+ PDK::Util::Filesystem.cp(vendored_gemfile_lock, File.join(PDK::Util.module_root, 'Gemfile.lock'))
134
133
  else
135
134
  argv = ['lock']
136
135
 
@@ -212,7 +211,7 @@ module PDK
212
211
 
213
212
  def binstubs!(gems)
214
213
  binstub_dir = File.join(File.dirname(gemfile), 'bin')
215
- return true if gems.all? { |gem| File.file?(File.join(binstub_dir, gem)) }
214
+ return true if gems.all? { |gem| PDK::Util::Filesystem.file?(File.join(binstub_dir, gem)) }
216
215
 
217
216
  cmd = bundle_command('binstubs', *gems, '--force')
218
217
  result = cmd.execute!
@@ -0,0 +1,115 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Util
5
+ module ChangelogGenerator
6
+ # Taken from the version regex in https://forgeapi.puppet.com/schemas/module.json
7
+ VERSION_REGEX = %r{^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$}
8
+
9
+ # Raises if the github_changelog_generator is not available
10
+ def self.github_changelog_generator_available!
11
+ require 'bundler'
12
+ raise PDK::CLI::ExitWithError, _('Unable to generate the changelog as the github_changelog_generator gem is not installed') unless Bundler.rubygems.find_name('github_changelog_generator').any?
13
+ end
14
+
15
+ # Runs the Changelog Generator gem (in the module's context) to automatically create a CHANGLELOG.MD file
16
+ #
17
+ # @returns [String] The content of the new Changelog
18
+ def self.generate_changelog
19
+ github_changelog_generator_available!
20
+
21
+ changelog_command = PDK::CLI::Exec::InteractiveCommand.new(PDK::CLI::Exec.bundle_bin, 'exec', 'rake', 'changelog')
22
+ changelog_command.context = :module
23
+
24
+ result = changelog_command.execute!
25
+ raise PDK::CLI::ExitWithError, _('Error generating changelog: %{stdout}' % { stdout: result[:stdout] }) unless result[:exit_code].zero?
26
+
27
+ output = changelog_content
28
+
29
+ raise PDK::CLI::ExitWithError, _('The generated changelog contains uncategorized Pull Requests. Please label them and try again. See %{changelog_file} for more details' % { changelog_file: changelog_file }) if output =~ %r{UNCATEGORIZED PRS; GO LABEL THEM} # rubocop:disable Metrics/LineLength
30
+ output
31
+ end
32
+
33
+ # Computes the next version, based on the content of a changelog
34
+ #
35
+ # @param current_version [String, Gem::Version] The current version of the module
36
+ # @return [String] The new version. May be the same as the current version if there are no notable changes
37
+ def self.compute_next_version(current_version)
38
+ raise PDK::CLI::ExitWithError, _('Invalid version string %{version}' % { version: current_version }) unless current_version =~ VERSION_REGEX
39
+ version = Gem::Version.create(current_version).segments
40
+ PDK.logger.info _('Determing the target version from \'%{file}\'') % { file: changelog_file }
41
+
42
+ # Grab all lines that start with ## between from the latest changes
43
+ # For example given the changelog below
44
+
45
+ # ```
46
+ # # Change log
47
+ #
48
+ # All notable changes to this project will be documented in this file.
49
+ #
50
+ # ## [v4.0.0](https://github.com/puppetlabs/puppetlabs-inifile/tree/v4.
51
+ #
52
+ # [Full Changelog](https://github.com/puppetlabs/puppetlabs-inifile/com --+
53
+ # |
54
+ # ### Changed |
55
+ # |
56
+ # - pdksync - FM-8499 - remove ubuntu14 support [\#363](https://github. | It's this piece of text we are interested in
57
+ # |
58
+ # ### Added |
59
+ # |
60
+ # - FM-8402 add debian 10 support [\#352](https://github.com/puppetlabs |
61
+ # |
62
+ # ## [v3.1.0](https://github.com/puppetlabs/puppetlabs-inifile/tree/v3. |
63
+ # --+
64
+ # [Full Changelog](https://github.com/puppetlabs/puppetlabs-inifile/com
65
+ #
66
+ # ### Added
67
+ #
68
+ # - FM-8222 - Port Module inifile to Litmus [\#344](https://github.com/
69
+ # - \(FM-8154\) Add Windows Server 2019 support [\#340](https://github.
70
+ # - \(FM-8041\) Add RedHat 8 support [\#339](https://github.com/puppetl
71
+ # ````
72
+ data = ''
73
+ in_changelog_entry = false
74
+ changelog_content.each_line do |line|
75
+ line.strip!
76
+ if line.start_with?('[')
77
+ # We're leaving the latest changes so we can break
78
+ break if in_changelog_entry
79
+ in_changelog_entry = true
80
+ end
81
+ if in_changelog_entry && line.start_with?('##')
82
+ data += line
83
+ end
84
+ end
85
+
86
+ # Check for meta headers in first two header line matches
87
+ if data =~ %r{^### Changed}
88
+ # Major Version bump
89
+ version[0] += 1
90
+ version[1] = 0
91
+ version[2] = 0
92
+ elsif data =~ %r{^### Added}
93
+ # Minor Version bump
94
+ version[1] += 1
95
+ version[2] = 0
96
+ elsif data =~ %r{^### Fixed}
97
+ # Patch Version bump
98
+ version[2] += 1
99
+ end
100
+
101
+ version.join('.')
102
+ end
103
+
104
+ def self.changelog_file
105
+ # Default Changelog file is CHANGELOG.md, but also search for the .MD prefix as well.
106
+ @changelog_file ||= ['CHANGELOG.md', 'CHANGELOG.MD'].map { |file| PDK::Util::Filesystem.expand_path(file) }.find { |path| PDK::Util::Filesystem.file?(path) }
107
+ end
108
+
109
+ def self.changelog_content
110
+ return '' if changelog_file.nil?
111
+ PDK::Util::Filesystem.read_file(changelog_file, open_args: 'rb:utf-8')
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,4 +1,5 @@
1
1
  require 'pdk'
2
+ autoload :FileUtils, 'fileutils'
2
3
 
3
4
  module PDK
4
5
  module Util
@@ -17,8 +18,8 @@ module PDK
17
18
  end
18
19
  module_function :write_file
19
20
 
20
- def read_file(file, nil_on_error: false)
21
- File.read(file)
21
+ def read_file(file, nil_on_error: false, open_args: 'r')
22
+ File.read(file, open_args: Array(open_args))
22
23
  rescue => e
23
24
  raise e unless nil_on_error
24
25
  nil
@@ -58,6 +59,11 @@ module PDK
58
59
  end
59
60
  module_function :fnmatch
60
61
 
62
+ def fnmatch?(*args)
63
+ File.fnmatch?(*args)
64
+ end
65
+ module_function :fnmatch?
66
+
61
67
  def readable?(*args)
62
68
  File.readable?(*args)
63
69
  end
@@ -72,6 +78,60 @@ module PDK
72
78
  FileUtils.rm(*args)
73
79
  end
74
80
  module_function :rm
81
+
82
+ def rm_f(*args)
83
+ FileUtils.rm_f(*args)
84
+ end
85
+ module_function :rm_f
86
+
87
+ def rm_rf(*args)
88
+ FileUtils.rm_rf(*args)
89
+ end
90
+ module_function :rm_rf
91
+
92
+ def remove_entry_secure(*args)
93
+ FileUtils.remove_entry_secure(*args)
94
+ end
95
+ module_function :remove_entry_secure
96
+
97
+ def zero?(*args)
98
+ File.zero?(*args)
99
+ end
100
+ module_function :zero?
101
+
102
+ def stat(*args)
103
+ File.stat(*args)
104
+ end
105
+ module_function :stat
106
+
107
+ def symlink?(*args)
108
+ File.symlink?(*args)
109
+ end
110
+ module_function :symlink?
111
+
112
+ def cp(*args)
113
+ FileUtils.cp(*args)
114
+ end
115
+ module_function :cp
116
+
117
+ def mv(*args)
118
+ FileUtils.mv(*args)
119
+ rescue Errno::ENOENT
120
+ # PDK-1169 - FileUtils.mv raises Errno::ENOENT when moving files inside
121
+ # VMWare shared folders on Windows. So we need to catch this
122
+ # case, check if the file exists to see if the exception is
123
+ # legit and "move" the file with cp & rm.
124
+ src, dest, opts = args
125
+ raise unless File.exist?(src)
126
+
127
+ FileUtils.cp(src, dest, preserve: true)
128
+ if (opts ||= {})[:secure]
129
+ FileUtils.remove_entry_secure(src, opts[:force])
130
+ else
131
+ FileUtils.remove_entry(src, opts[:force])
132
+ end
133
+ end
134
+ module_function :mv
75
135
  #:nocov:
76
136
  end
77
137
  end