Module: Omnibus::FileSyncer
Constant Summary collapse
- IGNORED_FILES =
Files to be ignored during a directory globbing
%w{. ..}.freeze
Instance Method Summary collapse
-
#all_files_under(source, options = {}) ⇒ Array<String>
Glob for all files under a given path/pattern, removing Ruby’s dumb idea to include ‘.’ and ‘..’ as entries.
-
#glob(pattern) ⇒ Array<String>
Glob across the given pattern, accounting for dotfiles, removing Ruby’s dumb idea to include ‘.’ and ‘..’ as entries.
-
#sync(source, destination, options = {}) ⇒ true
Copy the files from
source
todestination
, while removing any files indestination
that are not present insource
.
Instance Method Details
#all_files_under(source, options = {}) ⇒ Array<String>
Glob for all files under a given path/pattern, removing Ruby’s dumb idea to include ‘.’ and ‘..’ as entries.
57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/omnibus/file_syncer.rb', line 57 def all_files_under(source, = {}) excludes = Array([:exclude]).map do |exclude| [exclude, "#{exclude}/**"] end.flatten source_files = glob(File.join(source, "**/*")) source_files = source_files.reject do |source_file| basename = relative_path_for(source_file, source) excludes.any? { |exclude| File.fnmatch?(exclude, basename, File::FNM_DOTMATCH | File::FNM_PATHNAME) } end end |
#glob(pattern) ⇒ Array<String>
Glob across the given pattern, accounting for dotfiles, removing Ruby’s dumb idea to include ‘.’ and ‘..’ as entries.
36 37 38 39 40 41 42 |
# File 'lib/omnibus/file_syncer.rb', line 36 def glob(pattern) pattern = Pathname.new(pattern).cleanpath.to_s Dir.glob(pattern, File::FNM_DOTMATCH).sort.reject do |file| basename = File.basename(file) IGNORED_FILES.include?(basename) end end |
#sync(source, destination, options = {}) ⇒ true
Copy the files from source
to destination
, while removing any files in destination
that are not present in source
.
The method accepts an optional :exclude
parameter to ignore files and folders that match the given pattern(s). Note the exclude pattern behaves on paths relative to the given source. If you want to exclude a nested directory, you will need to use something like **/directory.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/omnibus/file_syncer.rb', line 91 def sync(source, destination, = {}) unless File.directory?(source) raise ArgumentError, "`source' must be a directory, but was a " \ "`#{File.ftype(source)}'! If you just want to sync a file, use " \ "the `copy' method instead." end source_files = all_files_under(source, ) # Ensure the destination directory exists FileUtils.mkdir_p(destination) unless File.directory?(destination) # Copy over the filtered source files source_files.each do |source_file| relative_path = relative_path_for(source_file, source) # Create the parent directory parent = File.join(destination, File.dirname(relative_path)) FileUtils.mkdir_p(parent) unless File.directory?(parent) case File.ftype(source_file).to_sym when :directory FileUtils.mkdir_p("#{destination}/#{relative_path}") when :link target = File.readlink(source_file) Dir.chdir(destination) do FileUtils.ln_sf(target, "#{destination}/#{relative_path}") end when :file source_stat = File.stat(source_file) # Detect 'files' which are hard links and use ln instead of cp to # duplicate them, provided their source is in place already if hardlink? source_stat if existing = hardlink_sources[[source_stat.dev, source_stat.ino]] FileUtils.ln(existing, "#{destination}/#{relative_path}", force: true) else begin FileUtils.cp(source_file, "#{destination}/#{relative_path}") rescue Errno::EACCES FileUtils.cp_r(source_file, "#{destination}/#{relative_path}", remove_destination: true) end hardlink_sources.store([source_stat.dev, source_stat.ino], "#{destination}/#{relative_path}") end else # First attempt a regular copy. If we don't have write # permission on the File, open will probably fail with # EACCES (making it hard to sync files with permission # r--r--r--). Rescue this error and use cp_r's # :remove_destination option. begin FileUtils.cp(source_file, "#{destination}/#{relative_path}") rescue Errno::EACCES FileUtils.cp_r(source_file, "#{destination}/#{relative_path}", remove_destination: true) end end else raise "Unknown file type: `File.ftype(source_file)' at `#{source_file}'!" end end # Remove any files in the destination that are not in the source files destination_files = glob("#{destination}/**/*") # Calculate the relative paths of files so we can compare to the # source. relative_source_files = source_files.map do |file| relative_path_for(file, source) end relative_destination_files = destination_files.map do |file| relative_path_for(file, destination) end # Remove any extra files that are present in the destination, but are # not in the source list extra_files = relative_destination_files - relative_source_files extra_files.each do |file| FileUtils.rm_rf(File.join(destination, file)) end true end |