Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/gem-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
bundle exec rubocop

- name: Publish to RubyGems
if: matrix.ruby == '3.2'
if: matrix.ruby == '3.2' && github.ref_name == 'master'
run: |
mkdir -p $HOME/.gem
touch $HOME/.gem/credentials
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 0.1.15

- Readme: update screenshots in docs
- Consistent order of icons on Legend and Mappings
- DirectoryDiffer: implements only option
- Show Flags icons closer

# 0.1.14

- Render environment variables and mappings with a table
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
dotsync (0.1.14)
dotsync (0.1.15)
fileutils (~> 1.7.3)
find (~> 0.2.0)
listen (~> 3.9.0)
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ dest = "$DOTFILES_DIR/config/alacritty"
Each mapping entry supports the following options:

- **`force`**: A boolean (true/false) value. When set to `true`, it forces deletion of the destination folder before transferring files from the source. This is particularly useful when you need to ensure that the destination is clean before a transfer.
- **`only`**: An array of files or folders. This option ensures that only the specified files or folders from the `src` directory are transferred to the `dest` directory. Example:
```toml
[[push.mappings]]
src = "$XDG_CONFIG_HOME"
dest = "$DOTFILES_DIR/config"
only = ["config.yml", "themes"]

```
- **`ignore`**: An array of patterns or file names to exclude during the transfer. This allows you to specify files or folders that should not be copied from the source to the destination.

These options apply when the source is a directory and are relevant for both `push` and `pull` operations.
Expand Down
Binary file modified docs/images/dotsync_pull.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/dotsync_push.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion lib/dotsync/actions/concerns/mappings_transfer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module MappingsTransfer

LEGEND = [
[Dotsync::Icons.force, "The source will overwrite the destination"],
[Dotsync::Icons.only, " Only paths configured to considered in the source"],
[Dotsync::Icons.only, "Paths designated explicitly as source only"],
[Dotsync::Icons.ignore, "Paths configured to be ignored in the destination"],
[Dotsync::Icons.invalid, "Invalid paths detected in the source or destination"]
]
Expand Down
36 changes: 26 additions & 10 deletions lib/dotsync/models/mapping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def initialize(attributes)
@original_only = Array(attributes["only"])
@force = attributes["force"] || false

@sanitized_src, @sanitized_dest, @sanitized_ignore, @sanitized_only = process_paths(
@sanitized_src, @sanitized_dest, @sanitized_ignores, @sanitized_only = process_paths(
@original_src,
@original_dest,
@original_ignores,
Expand All @@ -30,7 +30,7 @@ def dest
end

def ignores
@sanitized_ignore
@sanitized_ignores
end

def inclusions
Expand Down Expand Up @@ -77,11 +77,11 @@ def backup_basename

def icons
msg = []
msg << Icons.invalid unless valid?
msg << Icons.only if only?
msg << Icons.ignore if ignores?
msg << Icons.force if force?
msg.join(" ")
msg << Icons.only if has_inclusions?
msg << Icons.ignore if has_ignores?
msg << Icons.invalid unless valid?
msg.join
end

def to_s
Expand Down Expand Up @@ -114,25 +114,41 @@ def apply_to(path)
)
end

def include?(path)
return true unless has_inclusions?
return true if path == src
inclusions.any? { |inclusion| path_is_parent_or_same?(inclusion, path) }
end

def bidirectional_include?(path)
return true unless has_inclusions?
return true if path == src
inclusions.any? { |inclusion| path_is_parent_or_same?(inclusion, path) || path_is_parent_or_same?(path, inclusion) }
end

def ignore?(path)
ignores.any? { |ignore| path.start_with?(ignore) }
end

private
def ignores?
def has_ignores?
@original_ignores.any?
end

def only?
def has_inclusions?
@original_only.any?
end

def process_paths(raw_src, raw_dest, raw_ignores, raw_only)
sanitized_src = sanitize_path(raw_src)
sanitized_dest = sanitize_path(raw_dest)
sanitized_ignore = raw_ignores.flat_map do |path|
sanitized_ignores = raw_ignores.flat_map do |path|
[File.join(sanitized_src, path), File.join(sanitized_dest, path)]
end
sanitized_only = raw_only.map do |path|
File.join(sanitized_src, path)
end
[sanitized_src, sanitized_dest, sanitized_ignore, sanitized_only]
[sanitized_src, sanitized_dest, sanitized_ignores, sanitized_only]
end
end
end
5 changes: 5 additions & 0 deletions lib/dotsync/utils/directory_differ.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ def diff_mapping_directories
Find.find(mapping_src) do |src_path|
rel_path = src_path.sub(/^#{Regexp.escape(mapping_src)}\/?/, "")

unless @mapping.include?(src_path)
Find.prune
next
end

dest_path = File.join(mapping_dest, rel_path)

if !File.exist?(dest_path)
Expand Down
23 changes: 10 additions & 13 deletions lib/dotsync/utils/file_transfer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

module Dotsync
class FileTransfer
attr_reader :ignores

# Initializes a new FileTransfer instance
#
# @param mapping [Dotsync::Mapping] the mapping object containing source, destination, force, and ignore details
Expand All @@ -12,6 +10,7 @@ class FileTransfer
# @option mapping [Boolean] :force? optional flag to force actions
# @option mapping [Array<String>] :ignores optional list of files/directories to ignore
def initialize(mapping)
@mapping = mapping
@src = mapping.src
@dest = mapping.dest
@force = mapping.force?
Expand All @@ -29,19 +28,26 @@ def transfer
end

private
attr_reader :mapping, :ignores

def transfer_file(file_src, file_dest)
FileUtils.mkdir_p(File.dirname(file_dest))
FileUtils.cp(file_src, file_dest)
end

def transfer_folder(folder_src, folder_dest)
FileUtils.mkdir_p(folder_dest)

# `Dir.glob("#{folder_src}/*")` retrieves only the immediate contents
# (files and directories) within the specified directory (`folder_src`),
# without descending into subdirectories.

Dir.glob("#{folder_src}/*", File::FNM_DOTMATCH).each do |path|
next if [".", ".."].include?(File.basename(path))

full_path = File.expand_path(path)
next unless inclusion?(full_path)
next if ignore?(full_path)
next unless mapping.bidirectional_include?(full_path)
next if mapping.ignore?(full_path)

target = File.join(folder_dest, File.basename(path))
if File.file?(full_path)
Expand Down Expand Up @@ -77,14 +83,5 @@ def cleanup_folder(target_dir, exclusions = [])
end
end
end

def inclusion?(path)
return true unless @inclusions.any?
@inclusions.any? { |inclusion| path.start_with?(inclusion) || inclusion.start_with?(path) }
end

def ignore?(path)
@ignores.any? { |ignore| path.start_with?(ignore) }
end
end
end
6 changes: 6 additions & 0 deletions lib/dotsync/utils/path_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ def relative_to_absolute(paths, base_path)
paths.map { |path| File.join(base_path, path) }
end

def path_is_parent_or_same?(parent, child)
parent = Pathname.new(parent).expand_path
child = Pathname.new(child).expand_path
child.ascend.any? { |ancestor| ancestor == parent }
end

# Translates /tmp paths to /private/tmp paths on macOS
# Retains other paths as-is
# @param [String] path The input path to translate
Expand Down
2 changes: 1 addition & 1 deletion lib/dotsync/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Dotsync
VERSION = "0.1.14"
VERSION = "0.1.15"
end
2 changes: 1 addition & 1 deletion spec/dotsync/actions/support/logger_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def expect_show_mappings_legend
expect(logger).to receive(:info).with("Legend:", icon: :legend).ordered
expect_logger_log_table([
[Dotsync::Icons.force, "The source will overwrite the destination"],
[Dotsync::Icons.only, " Only paths configured to considered in the source"],
[Dotsync::Icons.only, "Paths designated explicitly as source only"],
[Dotsync::Icons.ignore, "Paths configured to be ignored in the destination"],
[Dotsync::Icons.invalid, "Invalid paths detected in the source or destination"]
])
Expand Down
35 changes: 34 additions & 1 deletion spec/dotsync/utils/directory_differ_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
let(:root) { File.join("/tmp", "dotsync") }
let(:src) { File.join(root, "src") }
let(:dest) { File.join(root, "dest") }
let(:only) { [] }
let(:ignore) { [] }
let(:mapping) do
Dotsync::Mapping.new(
"src" => mapping_src,
"dest" => mapping_dest,
"force" => true,
"only" => only,
"ignore" => ignore
)
end
Expand Down Expand Up @@ -63,7 +65,38 @@
end
end

context "with directory ignored" do
context "with only option" do
context "including a file" do
let(:only) { [File.join("fold", "file2.txt")] }

it "returns a Diff object with correct additions, modifications, and removals" do
diff = differ.diff

expect(diff).to be_a(Dotsync::Diff)
expect(diff.additions).to_not include(File.join(dest, "fold", "file1.txt"))
expect(diff.removals).to include(File.join(src, "fold", "file2.txt"))
expect(diff.modifications).to_not include(File.join(dest, "fold", "file3.txt"))
end
end

context "including a directory" do
let(:only) { [File.join("fold")] }

before do
FileUtils.mkdir_p(File.join(src, "fold2"))
File.write(File.join(src, "fold2", "file4.txt"), "content")
end

it "returns a Diff object with correct additions, modifications, and removals" do
diff = differ.diff

expect(diff).to be_a(Dotsync::Diff)
expect(diff.additions).to_not include(File.join(dest, "fold2", "file4.txt"))
end
end
end

context "with ignore option" do
let(:ignore) { ["fold"] }

it "returns a Diff without ignored directory" do
Expand Down