dotsync 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e9f29b4444bb3395cb499524272b6a1b4ca3071c4055970de7c4c3990ed8d29
4
- data.tar.gz: 9a1e346bb050beb363cb99a5a6f1476d50523cf37bfcc1d9b8dd30038fe74403
3
+ metadata.gz: b3c840b83736169eaabf3296862872f088fc9ae49dc15545adf49bf75439620e
4
+ data.tar.gz: c8cf364077464a687df689c9bc6f43aa75fa45c61df531fcdc498a043ab2b02d
5
5
  SHA512:
6
- metadata.gz: fbc66575cbb99d7dab60eb1d623058f2c9cc1b13648004a0b14cdaaed28c19c2a6c1f9b13454b90d4acac50566df804df946f94f7104aac0f1b0e161f2b6eb1b
7
- data.tar.gz: 150d2c742dca25f35baa5e288df8a21fa3ab34b4b236769de12f2f183f2064953fbe744494a8e505886cf780ce25b71f5efd05b0967ce0966ac83484e53bf2cc
6
+ metadata.gz: 105b36727336e5169a23de08a2a8f2165a568682a8ea4f590f93d93525990181cf3994239151d9a1e72d75e33416c217777a604373a37fbd0d7ff3e1aca94d93
7
+ data.tar.gz: 97a767742387ebd6af332e8e1a1d4e19b887c019fe5fb4863d449ea9e9ba6c9ddcf8459bee0a018362761d7f2c4d699bd7b69240af45ebc144c9ded697d46115
data/AGENTS.md ADDED
@@ -0,0 +1,93 @@
1
+ # Agents
2
+
3
+ This document describes AI agents and automation helpers that can assist with developing and maintaining the Dotsync project.
4
+
5
+ ## Development Agents
6
+
7
+ ### Code Review Agent
8
+
9
+ **Purpose**: Review code changes for quality, consistency, and adherence to Ruby best practices.
10
+
11
+ **When to use**:
12
+ - Before submitting pull requests
13
+ - After implementing new features
14
+ - When refactoring existing code
15
+
16
+ **What it checks**:
17
+ - Ruby style guide compliance (follows .rubocop.yml)
18
+ - Test coverage for new functionality
19
+ - Proper error handling
20
+ - Documentation completeness
21
+ - Performance considerations
22
+
23
+ ### Test Generation Agent
24
+
25
+ **Purpose**: Generate and enhance RSpec tests for Dotsync functionality.
26
+
27
+ **When to use**:
28
+ - When adding new actions or utilities
29
+ - When test coverage is insufficient
30
+ - When refactoring existing code
31
+
32
+ **Focus areas**:
33
+ - Unit tests for models (Mapping, Diff)
34
+ - Integration tests for actions (PullAction, PushAction, WatchAction)
35
+ - Edge cases and error scenarios
36
+ - File system operations
37
+
38
+ ### Documentation Agent
39
+
40
+ **Purpose**: Maintain and improve project documentation.
41
+
42
+ **When to use**:
43
+ - After adding new features
44
+ - When configuration options change
45
+ - When updating usage examples
46
+
47
+ **Responsibilities**:
48
+ - Keep README.md synchronized with code
49
+ - Update inline code documentation
50
+ - Maintain CHANGELOG.md
51
+ - Generate usage examples
52
+
53
+ ## Maintenance Agents
54
+
55
+ ### Dependency Update Agent
56
+
57
+ **Purpose**: Monitor and suggest updates for gem dependencies.
58
+
59
+ **What it monitors**:
60
+ - Security vulnerabilities in dependencies
61
+ - New versions of runtime and development dependencies
62
+ - Ruby version compatibility
63
+
64
+ ### Release Agent
65
+
66
+ **Purpose**: Assist with the release process following RELEASING.md guidelines.
67
+
68
+ **Checklist**:
69
+ - Version number updated in version.rb
70
+ - CHANGELOG.md updated with changes
71
+ - Tests passing
72
+ - RuboCop compliance
73
+ - Tag creation and push
74
+ - Gem publication to rubygems.org
75
+
76
+ ## Usage
77
+
78
+ To work with these agents effectively:
79
+
80
+ 1. **Be specific**: Provide clear context about what you're working on
81
+ 2. **Reference files**: Point to specific files or line numbers when discussing issues
82
+ 3. **Run tests**: Always run `rake spec` after changes
83
+ 4. **Follow conventions**: Adhere to existing code patterns and Ruby style guide
84
+
85
+ ## Contributing
86
+
87
+ When working with agents on this project:
88
+
89
+ - Review generated code carefully before committing
90
+ - Ensure all tests pass (`rake spec`)
91
+ - Run RuboCop (`bundle exec rubocop`)
92
+ - Update documentation as needed
93
+ - Follow the project's [Code of Conduct](CODE_OF_CONDUCT.md)
data/CHANGELOG.md CHANGED
@@ -1,3 +1,56 @@
1
+ # 0.1.20
2
+
3
+ **Robustness & Error Handling:**
4
+ - Add specific error classes for better error handling (`PermissionError`, `DiskFullError`, `SymlinkError`, `TypeConflictError`)
5
+ - Add symlink support with proper preservation of link targets (regular, broken, and relative symlinks)
6
+ - Add type conflict detection to prevent overwriting directories with files or vice versa
7
+ - Enhance FileTransfer error handling for permission issues and disk space errors
8
+
9
+ **Testing & Quality:**
10
+ - Add 16 new test cases covering edge cases and error scenarios
11
+ - Add comprehensive symlink handling tests (regular, broken, relative)
12
+ - Add path traversal security validation tests
13
+ - Add Unicode filename compatibility tests (Russian, Japanese, Chinese, emoji)
14
+ - Add empty directory transfer tests
15
+ - Add Mapping#apply_to tests for path handling and force flag preservation
16
+ - Improve content comparison tests to verify actual file changes
17
+ - Improve path validation tests with more edge cases
18
+ - Total test count increased from 136 to 152 examples
19
+
20
+ **Developer Experience:**
21
+ - All tests passing (152 examples, 0 failures)
22
+ - RuboCop compliant with no offenses
23
+
24
+ # 0.1.19
25
+
26
+ **Documentation & Testing:**
27
+ - Add comprehensive icons test suite with 40 test cases covering all icon functionality
28
+ - Add icons customization documentation section to README with complete examples
29
+ - Add "What is Dotsync?" overview section highlighting 7 key features
30
+ - Add Table of Contents for improved README navigation
31
+ - Add Quick Start guide with 5-step setup process
32
+ - Add Common Use Cases section with practical configuration examples (Neovim, Alacritty, shell configs)
33
+ - Add comprehensive Troubleshooting section covering 6 common issues and solutions
34
+ - Enhance Pro Tips section with 7 useful tips including environment variables and backup locations
35
+ - Add License and Ruby version badges to README
36
+ - Add IMPORTANT callout about --apply flag and preview mode behavior
37
+
38
+ **Bug Fixes:**
39
+ - Fix duplicate `src` typo in push/watch mapping examples (corrected to `dest`)
40
+ - Fix section title: "force and ignore" → "force, only, and ignore"
41
+ - Add defensive nil handling in `Icons.load_custom_icons` method
42
+
43
+ **Developer Experience:**
44
+ - Add AGENTS.md with AI agent guidelines for project development
45
+ - Improve user onboarding with clearer documentation and examples
46
+
47
+ # 0.1.18
48
+
49
+ - Add automatic version checking with non-intrusive upgrade prompts
50
+ - Version check runs once per 24 hours using cached timestamp
51
+ - Can be disabled with `DOTSYNC_NO_UPDATE_CHECK` environment variable
52
+ - Cache stored in XDG-compliant location (`~/.cache/dotsync/last_version_check`)
53
+
1
54
  # 0.1.17
2
55
 
3
56
  - Fixes skipped files
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dotsync (0.1.18)
4
+ dotsync (0.1.20)
5
5
  fileutils (~> 1.7.3)
6
6
  find (~> 0.2.0)
7
7
  listen (~> 3.9.0)
data/README.md CHANGED
@@ -2,11 +2,42 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/dotsync.svg)](https://rubygems.org/gems/dotsync)
4
4
  [![Ruby Gem Test Status](https://github.com/dsaenztagarro/dotsync/actions/workflows/gem-push.yml/badge.svg)](https://github.com/dsaenztagarro/dotsync/actions)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Ruby Version](https://img.shields.io/badge/ruby-%3E%3D%203.2-blue)](https://www.ruby-lang.org/)
5
7
 
6
8
  > [!WARNING]
7
9
  > This gem is under active development. You can expect new changes that may not be backward-compatible.
8
10
 
9
- Welcome to Dotsync! This gem helps you manage and synchronize your dotfiles effortlessly. Below you'll find information on installation, usage, and some tips for getting started.
11
+ ## What is Dotsync?
12
+
13
+ Dotsync is a powerful Ruby gem for managing and synchronizing your dotfiles across machines. Whether you're setting up a new development environment or keeping configurations in sync, Dotsync makes it effortless.
14
+
15
+ **Key Features:**
16
+ - **Bidirectional Sync**: Push local dotfiles to your repository or pull from repository to local machine
17
+ - **Preview Mode**: See what changes would be made before applying them (dry-run by default)
18
+ - **Smart Filtering**: Use `force`, `only`, and `ignore` options to precisely control what gets synced
19
+ - **Automatic Backups**: Pull operations create timestamped backups for easy recovery
20
+ - **Live Watching**: Continuously monitor and sync changes in real-time with `watch` command
21
+ - **Customizable Output**: Control verbosity and customize icons to match your preferences
22
+ - **Auto-Updates**: Get notified when new versions are available
23
+
24
+ ## Table of Contents
25
+
26
+ - [Requirements](#requirements)
27
+ - [Installation](#installation)
28
+ - [Quick Start](#quick-start)
29
+ - [Usage](#usage)
30
+ - [Executable Commands](#executable-commands)
31
+ - [Configuration](#configuration)
32
+ - [Customizing Icons](#customizing-icons)
33
+ - [Automatic Update Checks](#automatic-update-checks)
34
+ - [Pro Tips](#pro-tips)
35
+ - [Common Use Cases](#common-use-cases)
36
+ - [Troubleshooting](#troubleshooting)
37
+ - [Development](#development)
38
+ - [Contributing](#contributing)
39
+ - [License](#license)
40
+ - [Code of Conduct](#code-of-conduct)
10
41
 
11
42
  ![dotsync options](docs/images/dotsync_options.png)
12
43
 
@@ -29,12 +60,48 @@ Or install it yourself as:
29
60
 
30
61
  $ gem install dotsync
31
62
 
63
+ ## Quick Start
64
+
65
+ Get started with Dotsync in just a few steps:
66
+
67
+ 1. **Install the gem**:
68
+ ```shell
69
+ gem install dotsync
70
+ ```
71
+
72
+ 2. **Generate a default configuration**:
73
+ ```shell
74
+ dotsync setup
75
+ ```
76
+ This creates `~/.config/dotsync.toml` with example mappings.
77
+
78
+ 3. **Edit the configuration** (`~/.config/dotsync.toml`) to define your dotfile mappings:
79
+ ```toml
80
+ [[pull.mappings]]
81
+ src = "$HOME/dotfiles/config"
82
+ dest = "$HOME/.config"
83
+ ```
84
+
85
+ 4. **Preview your changes** (dry-run mode):
86
+ ```shell
87
+ dotsync pull
88
+ ```
89
+ This shows what would be changed without modifying any files.
90
+
91
+ 5. **Apply changes** when you're ready:
92
+ ```shell
93
+ dotsync pull --apply
94
+ ```
95
+
32
96
  ## Usage
33
97
 
34
98
  ### Executable Commands
35
99
 
36
100
  Dotsync provides the following commands to manage your dotfiles:
37
101
 
102
+ > [!IMPORTANT]
103
+ > By default, both `push` and `pull` commands run in **preview mode** (dry-run). They will show you what changes would be made without actually modifying any files. To apply changes, you **must** use the `--apply` flag.
104
+
38
105
  - **Push**: Transfer dotfiles from your local machine to the destination repository.
39
106
  ```shell
40
107
  dotsync push --apply [OPTION]
@@ -108,7 +175,7 @@ dest = "$HOME"
108
175
 
109
176
  [[push.mappings]]
110
177
  src = "$HOME/.zshenv"
111
- src = "$HOME_MIRROR/.zshenv"
178
+ dest = "$HOME_MIRROR/.zshenv"
112
179
 
113
180
  [[push.mappings]]
114
181
  src = "$XDG_CONFIG_HOME/alacritty"
@@ -119,7 +186,7 @@ only = ["alacritty.toml", "rose-pine.toml"]
119
186
 
120
187
  [[watch.mappings]]
121
188
  src = "$HOME/.zshenv"
122
- src = "$HOME_MIRROR/.zshenv"
189
+ dest = "$HOME_MIRROR/.zshenv"
123
190
 
124
191
  [[watch.mappings]]
125
192
  src = "$XDG_CONFIG_HOME/alacritty"
@@ -133,7 +200,7 @@ dest = "$DOTFILES_DIR/config/alacritty"
133
200
  > export XDG_CONFIG_HOME_MIRROR="$HOME/Code/dotfiles/xdg_config_home"
134
201
  > ```
135
202
 
136
- #### `force` and `ignore` Options in Mappings
203
+ #### `force`, `only`, and `ignore` Options in Mappings
137
204
 
138
205
  Each mapping entry supports the following options:
139
206
 
@@ -150,34 +217,97 @@ Each mapping entry supports the following options:
150
217
 
151
218
  These options apply when the source is a directory and are relevant for both `push` and `pull` operations.
152
219
 
153
- ### Rendering Mappings with Icons
220
+ ### Customizing Icons
154
221
 
155
- When running `push` or `pull` actions, the mappings are rendered in the console with relevant icons to provide visual feedback on the status of each mapping. To correctly view these icons, ensure you are using a terminal that supports a patched [Nerd Font](https://www.nerdfonts.com). Below are some examples of how the mappings are displayed:
222
+ Dotsync allows you to customize the icons displayed in the console output by adding an `[icons]` section to your configuration file (`~/.config/dotsync.toml`). This is useful if you prefer different icons or need compatibility with terminals that don't support Nerd Fonts.
156
223
 
157
- - **Force Icon**:
158
- ```
159
- Mappings:
160
- $DOTFILES_DIR/config/ → $XDG_CONFIG_HOME ⚡
161
- ```
162
- The ⚡ icon (`Dotsync::Icons::FORCE`) indicates that the `force` option is enabled and the destination folder will be cleared before the transfer.
224
+ #### Available Icon Options
163
225
 
164
- - **Ignore Icon**:
165
- ```
166
- Mappings:
167
- $DOTFILES_DIR/home/.zshenv $HOME 🚫
168
- ```
169
- The 🚫 icon (`Dotsync::Icons::IGNORE`) indicates that certain files or patterns are being ignored during the transfer.
226
+ You can customize the following icons in your configuration:
227
+
228
+ **Mapping Status Icons** (shown next to each mapping):
229
+ - `force` - Indicates force deletion is enabled (clears destination before transfer)
230
+ - `only` - Indicates only specific files will be transferred
231
+ - `ignore` - Indicates files are being ignored during transfer
232
+ - `invalid` - Indicates the mapping is invalid (missing source/destination)
233
+
234
+ **Difference Status Icons** (shown in diff output):
235
+ - `diff_created` - Shows newly created/added files
236
+ - `diff_updated` - Shows updated/modified files
237
+ - `diff_removed` - Shows removed/deleted files
238
+
239
+ #### Example Configuration
240
+
241
+ Here's a complete example showing all customizable icons using UTF-8 emojis (works without Nerd Fonts):
242
+
243
+ ```toml
244
+ [icons]
245
+ # Mapping status icons
246
+ force = "⚡" # Force deletion enabled
247
+ only = "📋" # Only specific files transferred
248
+ ignore = "🚫" # Files ignored during transfer
249
+ invalid = "❌" # Invalid mapping
250
+
251
+ # Diff status icons
252
+ diff_created = "✨" # New files created
253
+ diff_updated = "📝" # Files modified
254
+ diff_removed = "🗑️ " # Files deleted
255
+
256
+ # Example mappings section
257
+ [[pull.mappings]]
258
+ src = "$XDG_CONFIG_HOME_MIRROR"
259
+ dest = "$XDG_CONFIG_HOME"
260
+ ignore = ["cache"]
261
+ ```
170
262
 
171
- - **Invalid Icon**:
263
+ #### Default Icons
264
+
265
+ If you don't specify custom icons, Dotsync uses [Nerd Font](https://www.nerdfonts.com) icons by default. These icons will only display correctly if you're using a terminal with a patched Nerd Font installed.
266
+
267
+ | Icon | Default (Nerd Font) | Nerd Font Code | Purpose |
268
+ |------|---------------------|----------------|---------|
269
+ | `force` | `󰁪 ` | `nf-md-lightning_bolt` | Force deletion enabled |
270
+ | `only` | ` ` | `nf-md-filter` | Only mode active |
271
+ | `ignore` | `󰈉 ` | `nf-md-cancel` | Ignoring files |
272
+ | `invalid` | `󱏏 ` | `nf-md-alert_octagram` | Invalid mapping |
273
+ | `diff_created` | ` ` | `nf-md-plus` | File created |
274
+ | `diff_updated` | ` ` | `nf-md-pencil` | File updated |
275
+ | `diff_removed` | ` ` | `nf-md-minus` | File removed |
276
+
277
+ > [!NOTE]
278
+ > The icons in the "Default (Nerd Font)" column may not be visible unless you're viewing this with a Nerd Font. You can find these icons at [nerdfonts.com](https://www.nerdfonts.com/cheat-sheet) by searching for the Nerd Font Code.
279
+
280
+ > [!TIP]
281
+ > You can set any icon to an empty string (`""`) to hide it completely, or use any UTF-8 character or emoji. The `dotsync setup` command generates a configuration file with some example custom icons to get you started.
282
+
283
+ ### Automatic Update Checks
284
+
285
+ Dotsync automatically checks for new versions once per day and notifies you if an update is available. This check is non-intrusive and will not interrupt your workflow.
286
+
287
+ To disable automatic update checks:
288
+ - Set environment variable: `export DOTSYNC_NO_UPDATE_CHECK=1`
289
+
290
+ The check runs after your command completes and uses a cached timestamp to avoid excessive API calls. The cache is stored in `~/.cache/dotsync/last_version_check` following the XDG Base Directory specification.
291
+
292
+ ### Pro Tips
293
+
294
+ - **Preview Before Applying**: Always run commands without `--apply` first to preview changes:
295
+ ```shell
296
+ dotsync pull # Preview changes
297
+ dotsync pull --apply # Apply after reviewing
172
298
  ```
173
- Mappings:
174
- $DOTFILES_DIR/home/.vimrc $HOME
299
+
300
+ - **Using Environment Variables**: Simplify your configuration with mirror environment variables:
301
+ ```bash
302
+ # Add to your ~/.zshrc or ~/.bashrc
303
+ export DOTFILES_DIR="$HOME/dotfiles"
304
+ export XDG_CONFIG_HOME_MIRROR="$DOTFILES_DIR/config"
305
+ export HOME_MIRROR="$DOTFILES_DIR/home"
175
306
  ```
176
- The ❌ icon (`Dotsync::Icons::INVALID`) indicates that the mapping is invalid due to missing source or destination paths.
177
307
 
178
- ### Pro Tips
308
+ - **Backup Location**: Pull operations automatically backup files to `~/.cache/dotsync/backups/` with timestamps. Only the 10 most recent backups are kept.
179
309
 
180
- - **Using rbenv**: To ensure the gem uses the correct Ruby version managed by rbenv, you can run:
310
+ - **Using rbenv**: To ensure the gem uses the correct Ruby version managed by rbenv:
181
311
  ```shell
182
312
  RBENV_VERSION=3.2.0 dotsync push
183
313
  ```
@@ -187,6 +317,127 @@ When running `push` or `pull` actions, the mappings are rendered in the console
187
317
  gem install dotsync
188
318
  ```
189
319
 
320
+ - **Disable Update Checks**: If you prefer not to see update notifications:
321
+ ```shell
322
+ export DOTSYNC_NO_UPDATE_CHECK=1
323
+ ```
324
+
325
+ - **Quiet Mode**: For use in scripts or when you only want to see errors:
326
+ ```shell
327
+ dotsync pull --apply --quiet
328
+ ```
329
+
330
+ ## Common Use Cases
331
+
332
+ Here are some practical examples of how to use Dotsync for popular configuration files:
333
+
334
+ ### Syncing Neovim Configuration
335
+
336
+ ```toml
337
+ [[pull.mappings]]
338
+ src = "$HOME/dotfiles/config/nvim"
339
+ dest = "$HOME/.config/nvim"
340
+ force = true
341
+ ignore = ["lazy-lock.json", ".luarc.json"]
342
+ ```
343
+
344
+ ### Syncing Terminal Emulator (Alacritty)
345
+
346
+ ```toml
347
+ [[push.mappings]]
348
+ src = "$HOME/.config/alacritty"
349
+ dest = "$HOME/dotfiles/config/alacritty"
350
+ only = ["alacritty.toml", "themes"]
351
+ ```
352
+
353
+ ### Syncing Shell Configuration
354
+
355
+ ```toml
356
+ [[pull.mappings]]
357
+ src = "$HOME/dotfiles/shell/.zshrc"
358
+ dest = "$HOME"
359
+
360
+ [[pull.mappings]]
361
+ src = "$HOME/dotfiles/shell/.zshenv"
362
+ dest = "$HOME"
363
+ ```
364
+
365
+ ### Syncing Multiple Config Directories
366
+
367
+ ```toml
368
+ [[pull.mappings]]
369
+ src = "$HOME/dotfiles/config"
370
+ dest = "$HOME/.config"
371
+ ignore = ["nvim", "cache", "*.log"]
372
+ ```
373
+
374
+ ## Troubleshooting
375
+
376
+ ### Icons Not Displaying Correctly
377
+
378
+ **Problem**: Icons appear as boxes, question marks, or strange characters.
379
+
380
+ **Solution**:
381
+ - Install a [Nerd Font](https://www.nerdfonts.com/) and configure your terminal to use it
382
+ - Or customize icons in `~/.config/dotsync.toml` using UTF-8 emojis or regular characters:
383
+ ```toml
384
+ [icons]
385
+ force = "!"
386
+ only = "*"
387
+ ignore = "x"
388
+ invalid = "?"
389
+ diff_created = "+"
390
+ diff_updated = "~"
391
+ diff_removed = "-"
392
+ ```
393
+
394
+ ### Changes Not Being Applied
395
+
396
+ **Problem**: Running `dotsync push` or `dotsync pull` doesn't modify files.
397
+
398
+ **Solution**: Remember to use the `--apply` flag to apply changes. Without it, commands run in preview mode:
399
+ ```shell
400
+ dotsync pull --apply
401
+ ```
402
+
403
+ ### Permission Denied Errors
404
+
405
+ **Problem**: Getting permission errors when syncing files.
406
+
407
+ **Solution**:
408
+ - Ensure you have write permissions for destination directories
409
+ - Check file ownership: `ls -la ~/.config`
410
+ - For system directories, you may need to adjust your mappings to use user-writable locations
411
+
412
+ ### Source or Destination Not Found
413
+
414
+ **Problem**: Error messages about missing source or destination paths.
415
+
416
+ **Solution**:
417
+ - Verify environment variables are set correctly (e.g., `echo $XDG_CONFIG_HOME`)
418
+ - Use absolute paths in your configuration if environment variables aren't available
419
+ - Create destination directories before running pull: `mkdir -p ~/.config`
420
+
421
+ ### Restoring from Backups
422
+
423
+ **Problem**: Need to restore files after a pull operation.
424
+
425
+ **Solution**: Pull operations create automatic backups in `~/.cache/dotsync/backups/`:
426
+ ```shell
427
+ ls -la ~/.cache/dotsync/backups/
428
+ # Copy files from the timestamped backup directory
429
+ cp -r ~/.cache/dotsync/backups/YYYYMMDD_HHMMSS/* ~/.config/
430
+ ```
431
+
432
+ ### Watch Command Not Detecting Changes
433
+
434
+ **Problem**: `dotsync watch` doesn't sync changes automatically.
435
+
436
+ **Solution**:
437
+ - Verify your watch mappings are configured correctly in `~/.config/dotsync.toml`
438
+ - Ensure the source directories exist and are accessible
439
+ - Try stopping and restarting the watch command
440
+
190
441
  ## Development
191
442
 
192
443
  - After checking out the repo, run `bin/setup` to install dependencies.
@@ -3,4 +3,9 @@
3
3
  module Dotsync
4
4
  class Error < StandardError; end
5
5
  class ConfigError < StandardError; end
6
+ class FileTransferError < Error; end
7
+ class PermissionError < FileTransferError; end
8
+ class DiskFullError < FileTransferError; end
9
+ class SymlinkError < FileTransferError; end
10
+ class TypeConflictError < FileTransferError; end
6
11
  end
data/lib/dotsync/icons.rb CHANGED
@@ -43,6 +43,7 @@ module Dotsync
43
43
  @custom_icons = {}
44
44
 
45
45
  def self.load_custom_icons(config)
46
+ config ||= {}
46
47
  @custom_icons = {
47
48
  # Mappings Legend
48
49
  force: config.dig("icons", "force") || DEFAULT_FORCE,
@@ -58,11 +58,17 @@ module Dotsync
58
58
  end
59
59
 
60
60
  def valid?
61
+ return false unless paths_are_distinct?
62
+ return false unless paths_not_nested?
61
63
  directories? || files? || file_present_in_src_only?
62
64
  end
63
65
 
64
66
  def file_changed?
65
- files_present? && (File.size(src) != File.size(dest))
67
+ return false unless files_present?
68
+ # Check size first for quick comparison
69
+ return true if File.size(src) != File.size(dest)
70
+ # If sizes match, compare content
71
+ FileUtils.compare_file(src, dest) == false
66
72
  end
67
73
 
68
74
  def backup_possible?
@@ -154,5 +160,16 @@ module Dotsync
154
160
  end
155
161
  [sanitized_src, sanitized_dest, sanitized_ignores, sanitized_only]
156
162
  end
163
+
164
+ def paths_are_distinct?
165
+ src != dest
166
+ end
167
+
168
+ def paths_not_nested?
169
+ # Check if dest is inside src or vice versa
170
+ return false if dest.start_with?("#{src}/")
171
+ return false if src.start_with?("#{dest}/")
172
+ true
173
+ end
157
174
  end
158
175
  end
@@ -33,6 +33,8 @@ module Dotsync
33
33
  @logger.error("Error running '#{action_name}':")
34
34
  @logger.info(e.message)
35
35
  raise
36
+ ensure
37
+ check_for_updates
36
38
  end
37
39
  end
38
40
  end
@@ -77,6 +79,17 @@ module Dotsync
77
79
  @logger.info("Configuration file created at #{config_path}")
78
80
  end
79
81
 
82
+ # Check for available updates
83
+ def check_for_updates
84
+ return if ENV["DOTSYNC_NO_UPDATE_CHECK"]
85
+
86
+ checker = Dotsync::VersionChecker.new(Dotsync::VERSION, logger: @logger)
87
+ checker.check_for_updates if checker.should_check?
88
+ rescue => e
89
+ # Silently fail - never break the tool
90
+ @logger.log("Debug: Version check failed - #{e.message}") if ENV["DEBUG"]
91
+ end
92
+
80
93
  # Utility to convert 'pull' to 'Pull', 'sync' to 'Sync', etc.
81
94
  def camelize(str)
82
95
  str.split("_").map(&:capitalize).join
@@ -46,7 +46,7 @@ module Dotsync
46
46
  if !File.exist?(dest_path)
47
47
  additions << rel_path
48
48
  elsif File.file?(src_path) && File.file?(dest_path)
49
- if File.size(src_path) != File.size(dest_path)
49
+ if files_differ?(src_path, dest_path)
50
50
  modifications << rel_path
51
51
  end
52
52
  end
@@ -92,5 +92,13 @@ module Dotsync
92
92
  end
93
93
  end
94
94
  end
95
+
96
+ def files_differ?(src_path, dest_path)
97
+ # First check size for quick comparison
98
+ return true if File.size(src_path) != File.size(dest_path)
99
+
100
+ # If sizes match, compare content
101
+ FileUtils.compare_file(src_path, dest_path) == false
102
+ end
95
103
  end
96
104
  end
@@ -20,7 +20,28 @@ module Dotsync
20
20
 
21
21
  def transfer
22
22
  if File.file?(@src)
23
- transfer_file(@src, @dest)
23
+ # Check if we're trying to overwrite a directory with a file
24
+ if File.exist?(@dest) && File.directory?(@dest) && !File.symlink?(@dest)
25
+ # If @dest is a directory and NOT just a parent directory for the file,
26
+ # this is a conflict. The check is: if @dest path exactly matches where
27
+ # we want the file to be (not a parent dir), then it's a conflict.
28
+ # We determine this by checking if File.basename(@src) already appears
29
+ # to be accounted for in @dest path.
30
+ dest_basename = File.basename(@dest)
31
+ src_basename = File.basename(@src)
32
+
33
+ if dest_basename == src_basename
34
+ raise Dotsync::TypeConflictError, "Cannot overwrite directory '#{@dest}' with file '#{@src}'"
35
+ end
36
+ end
37
+
38
+ # If dest is a directory, compute the target file path
39
+ target_dest = if File.directory?(@dest)
40
+ File.join(@dest, File.basename(@src))
41
+ else
42
+ @dest
43
+ end
44
+ transfer_file(@src, target_dest)
24
45
  else
25
46
  cleanup_folder(@dest) if @force
26
47
  transfer_folder(@src, @dest)
@@ -31,8 +52,29 @@ module Dotsync
31
52
  attr_reader :mapping, :ignores
32
53
 
33
54
  def transfer_file(file_src, file_dest)
55
+ # Check for type conflicts before transfer
56
+ if File.exist?(file_dest) && File.directory?(file_dest)
57
+ raise Dotsync::TypeConflictError, "Cannot overwrite directory '#{file_dest}' with file '#{file_src}'"
58
+ end
59
+
34
60
  FileUtils.mkdir_p(File.dirname(file_dest))
35
- FileUtils.cp(file_src, file_dest)
61
+
62
+ # Use atomic write: copy to temp file, then rename
63
+ # This prevents corruption if copy is interrupted
64
+ temp_file = "#{file_dest}.tmp.#{Process.pid}"
65
+ begin
66
+ FileUtils.cp(file_src, temp_file)
67
+ FileUtils.mv(temp_file, file_dest, force: true)
68
+ rescue Errno::EACCES, Errno::EPERM => e
69
+ FileUtils.rm_f(temp_file) if File.exist?(temp_file)
70
+ raise Dotsync::PermissionError, "Permission denied: #{e.message}"
71
+ rescue Errno::ENOSPC => e
72
+ FileUtils.rm_f(temp_file) if File.exist?(temp_file)
73
+ raise Dotsync::DiskFullError, "Disk full: #{e.message}"
74
+ rescue StandardError => e
75
+ FileUtils.rm_f(temp_file) if File.exist?(temp_file)
76
+ raise Dotsync::FileTransferError, "Transfer failed: #{e.message}"
77
+ end
36
78
  end
37
79
 
38
80
  def transfer_folder(folder_src, folder_dest)
@@ -53,14 +95,42 @@ module Dotsync
53
95
  next if mapping.ignore?(full_path)
54
96
 
55
97
  target = File.join(folder_dest, File.basename(path))
56
- if File.file?(full_path)
57
- FileUtils.cp(full_path, target)
58
- else
98
+ if File.symlink?(full_path)
99
+ transfer_symlink(full_path, target)
100
+ elsif File.file?(full_path)
101
+ transfer_file(full_path, target)
102
+ elsif File.directory?(full_path)
59
103
  transfer_folder(full_path, target)
60
104
  end
61
105
  end
62
106
  end
63
107
 
108
+ def transfer_symlink(symlink_src, symlink_dest)
109
+ # Check if we're trying to overwrite a regular file or directory with a symlink
110
+ if File.exist?(symlink_dest) && !File.symlink?(symlink_dest)
111
+ if File.directory?(symlink_dest)
112
+ raise Dotsync::TypeConflictError, "Cannot overwrite directory '#{symlink_dest}' with symlink '#{symlink_src}'"
113
+ end
114
+ end
115
+
116
+ FileUtils.mkdir_p(File.dirname(symlink_dest))
117
+
118
+ # Get the target the symlink points to
119
+ link_target = File.readlink(symlink_src)
120
+
121
+ begin
122
+ # Remove existing symlink if present
123
+ FileUtils.rm(symlink_dest) if File.exist?(symlink_dest) || File.symlink?(symlink_dest)
124
+
125
+ # Create the new symlink
126
+ File.symlink(link_target, symlink_dest)
127
+ rescue Errno::EACCES, Errno::EPERM => e
128
+ raise Dotsync::PermissionError, "Permission denied creating symlink: #{e.message}"
129
+ rescue StandardError => e
130
+ raise Dotsync::SymlinkError, "Failed to create symlink: #{e.message}"
131
+ end
132
+ end
133
+
64
134
  def cleanup_folder(target_dir)
65
135
  target_dir = File.expand_path(target_dir)
66
136
 
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "time"
6
+ require "fileutils"
7
+
8
+ module Dotsync
9
+ class VersionChecker
10
+ include Dotsync::XDGBaseDirectory
11
+
12
+ RUBYGEMS_API_URL = "https://rubygems.org/api/v1/versions/dotsync/latest.json"
13
+ CHECK_INTERVAL = 86400 # 24 hours in seconds
14
+ REQUEST_TIMEOUT = 3 # seconds
15
+
16
+ def initialize(current_version, logger: nil)
17
+ @current_version = current_version
18
+ @logger = logger
19
+ end
20
+
21
+ # Returns true if we should check for updates (cache expired or doesn't exist)
22
+ def should_check?
23
+ return false if ENV["DOTSYNC_NO_UPDATE_CHECK"]
24
+ return true unless File.exist?(cache_file_path)
25
+
26
+ Time.now - last_check_time > CHECK_INTERVAL
27
+ rescue => e
28
+ # If we can't determine, be conservative and don't check
29
+ debug_log("Error checking if should check: #{e.message}")
30
+ false
31
+ end
32
+
33
+ # Main method to check for updates and display message if needed
34
+ def check_for_updates
35
+ return unless should_check?
36
+
37
+ latest_version = fetch_latest_version
38
+ return unless latest_version
39
+
40
+ update_cache
41
+
42
+ display_update_message(latest_version) if version_outdated?(@current_version, latest_version)
43
+ rescue => e
44
+ # Silently fail - never break the application
45
+ debug_log("Error checking for updates: #{e.message}")
46
+ end
47
+
48
+ private
49
+ def cache_file_path
50
+ @cache_file_path ||= File.join(xdg_cache_home, "dotsync", "last_version_check")
51
+ end
52
+
53
+ def last_check_time
54
+ return Time.at(0) unless File.exist?(cache_file_path)
55
+
56
+ Time.parse(File.read(cache_file_path).strip)
57
+ rescue => e
58
+ debug_log("Error reading cache time: #{e.message}")
59
+ Time.at(0)
60
+ end
61
+
62
+ def update_cache
63
+ FileUtils.mkdir_p(File.dirname(cache_file_path))
64
+ File.write(cache_file_path, Time.now.iso8601)
65
+ rescue => e
66
+ debug_log("Error updating cache: #{e.message}")
67
+ end
68
+
69
+ def fetch_latest_version
70
+ uri = URI(RUBYGEMS_API_URL)
71
+
72
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: REQUEST_TIMEOUT, read_timeout: REQUEST_TIMEOUT) do |http|
73
+ request = Net::HTTP::Get.new(uri)
74
+ http.request(request)
75
+ end
76
+
77
+ return nil unless response.is_a?(Net::HTTPSuccess)
78
+
79
+ data = JSON.parse(response.body)
80
+ data["version"]
81
+ rescue => e
82
+ debug_log("Error fetching latest version: #{e.message}")
83
+ nil
84
+ end
85
+
86
+ def version_outdated?(current, latest)
87
+ Gem::Version.new(current) < Gem::Version.new(latest)
88
+ rescue => e
89
+ debug_log("Error comparing versions: #{e.message}")
90
+ false
91
+ end
92
+
93
+ def display_update_message(latest_version)
94
+ msg = "\n"
95
+ msg += "\e[38;5;226m" # Yellow color
96
+ msg += "A new version of dotsync is available: #{latest_version} "
97
+ msg += "(current: #{@current_version})\n"
98
+ msg += "Run 'gem update dotsync' to upgrade"
99
+ msg += "\e[0m\n" # Reset color
100
+
101
+ $stderr.puts msg
102
+ end
103
+
104
+ def debug_log(message)
105
+ return unless ENV["DEBUG"]
106
+
107
+ if @logger
108
+ @logger.log("Debug: #{message}")
109
+ else
110
+ $stderr.puts "Debug: #{message}"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dotsync
4
- VERSION = "0.1.18"
4
+ VERSION = "0.1.20"
5
5
  end
data/lib/dotsync.rb CHANGED
@@ -17,19 +17,20 @@ require_relative "dotsync/colors"
17
17
  require_relative "dotsync/runner"
18
18
  require_relative "dotsync/version"
19
19
 
20
+ # Config Concerns (loaded early as they're used by other modules)
21
+ require_relative "dotsync/config/concerns/xdg_base_directory"
22
+
20
23
  # Utils
21
24
  require_relative "dotsync/utils/path_utils"
22
25
  require_relative "dotsync/utils/logger"
23
26
  require_relative "dotsync/utils/file_transfer"
24
27
  require_relative "dotsync/utils/directory_differ"
28
+ require_relative "dotsync/utils/version_checker"
25
29
 
26
30
  # Models
27
31
  require_relative "dotsync/models/mapping"
28
32
  require_relative "dotsync/models/diff"
29
33
 
30
- # Config Concerns
31
- require_relative "dotsync/config/concerns/xdg_base_directory"
32
-
33
34
  # Config
34
35
  require_relative "dotsync/config/base_config"
35
36
  require_relative "dotsync/config/pull_action_config"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dotsync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.18
4
+ version: 0.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Sáenz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-11-09 00:00:00.000000000 Z
11
+ date: 2025-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: toml-rb
@@ -262,6 +262,7 @@ files:
262
262
  - ".rspec"
263
263
  - ".rubocop.yml"
264
264
  - ".ruby-version"
265
+ - AGENTS.md
265
266
  - CHANGELOG.md
266
267
  - CODE_OF_CONDUCT.md
267
268
  - Gemfile
@@ -301,6 +302,7 @@ files:
301
302
  - lib/dotsync/utils/file_transfer.rb
302
303
  - lib/dotsync/utils/logger.rb
303
304
  - lib/dotsync/utils/path_utils.rb
305
+ - lib/dotsync/utils/version_checker.rb
304
306
  - lib/dotsync/version.rb
305
307
  homepage: https://github.com/dsaenztagarro/dotsync
306
308
  licenses: