dotsync 0.2.2 → 0.3.0
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 +4 -4
- data/CHANGELOG.md +55 -1
- data/Gemfile.lock +1 -1
- data/README.md +169 -22
- data/Rakefile +23 -9
- data/lib/dotsync/actions/concerns/mappings_transfer.rb +39 -2
- data/lib/dotsync/actions/pull_action.rb +2 -0
- data/lib/dotsync/actions/push_action.rb +2 -0
- data/lib/dotsync/config/concerns/sync_mappings.rb +42 -0
- data/lib/dotsync/config/pull_action_config.rb +19 -1
- data/lib/dotsync/config/push_action_config.rb +19 -1
- data/lib/dotsync/errors.rb +1 -0
- data/lib/dotsync/icons.rb +7 -0
- data/lib/dotsync/loaders/pull_loader.rb +1 -0
- data/lib/dotsync/loaders/push_loader.rb +1 -0
- data/lib/dotsync/models/mapping.rb +8 -0
- data/lib/dotsync/utils/config_cache.rb +28 -3
- data/lib/dotsync/utils/config_merger.rb +78 -0
- data/lib/dotsync/utils/hook_runner.rb +48 -0
- data/lib/dotsync/version.rb +1 -1
- data/lib/dotsync.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 710780a2db7e818a04b5eca4d4546b3fb6f085fc4b0eaba11df1bcab6bfc0ebd
|
|
4
|
+
data.tar.gz: 4e41f76afbbce3996c723e681d9af06d6babd2e69533b6b67ac687fe15e06864
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57e5da2e7b69b306a01709a5d6ebcc2e2d7cfd7fdd767bae7ab2bbc9957034abbd6dca04a9d0d237b6cd442ae6ac570c57bce15761ebabe865dc2730b1fb3558
|
|
7
|
+
data.tar.gz: adf1fb97f5fa7ca956653617c2d574f556e9bb6c9ed7407384e480de7f6694bd770776eed99d8a0474d7b1399d8f0616948e8e4d12e4aab0f242681371c2ab9f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
+
## [0.3.0] - 2026-02-14
|
|
2
|
+
|
|
3
|
+
**New Features:**
|
|
4
|
+
- Add `include` directive for config file composition
|
|
5
|
+
- Base config holds shared content; machine overlays contain only the delta
|
|
6
|
+
- `include = "base.toml"` — resolves path relative to the config file's directory
|
|
7
|
+
- Deep merge semantics: arrays concatenate, hashes merge recursively, scalars overlay wins
|
|
8
|
+
- Chained includes are rejected to keep behavior predictable
|
|
9
|
+
- Cache invalidation tracks included file's mtime and size alongside overlay file
|
|
10
|
+
- Zero downstream changes — `BaseConfig`, `PullActionConfig`, `PushActionConfig`, `SyncMappings` all see a plain merged hash
|
|
11
|
+
|
|
12
|
+
**New Files:**
|
|
13
|
+
- `lib/dotsync/utils/config_merger.rb` — ConfigMerger utility with resolve, deep merge, and include validation
|
|
14
|
+
- `spec/dotsync/utils/config_merger_spec.rb` — Comprehensive tests for ConfigMerger
|
|
15
|
+
|
|
16
|
+
**Documentation:**
|
|
17
|
+
- Add "Config Includes" section to README with usage examples and merge semantics
|
|
18
|
+
- Update "Per-Machine Configuration Files" section with include-based example
|
|
19
|
+
|
|
20
|
+
**Testing:**
|
|
21
|
+
- Add ConfigMerger specs: no-include passthrough, array concatenation, hash deep merge, scalar override, include key consumption, relative path resolution, missing file error, chained include rejection, non-string include error, base/overlay key preservation, empty overlay, include_path accessor
|
|
22
|
+
- Add include-aware ConfigCache specs: mtime/size invalidation, deleted include, metadata contains include stats, no-cache mode with includes
|
|
23
|
+
- Add end-to-end BaseConfig spec: base.toml + overlay.toml with include → to_h returns merged result
|
|
24
|
+
|
|
25
|
+
## [0.2.3] - 2026-02-08
|
|
26
|
+
|
|
27
|
+
**New Features:**
|
|
28
|
+
- Add per-mapping post-sync hooks that run commands after files are transferred
|
|
29
|
+
- `post_sync` — runs after sync in both directions (`[[sync]]` mappings)
|
|
30
|
+
- `post_push` — runs only after push (`[[sync]]` and `[[push]]` mappings)
|
|
31
|
+
- `post_pull` — runs only after pull (`[[sync]]` and `[[pull]]` mappings)
|
|
32
|
+
- Template variables: `{files}` (shell-quoted changed dest paths), `{src}`, `{dest}`
|
|
33
|
+
- Hooks only execute when files actually changed and only with `--apply`
|
|
34
|
+
- Hook failures log errors but do not abort remaining hooks or mappings
|
|
35
|
+
- Preview mode shows what commands would run without executing
|
|
36
|
+
- Add `HookError` error class for hook-related errors
|
|
37
|
+
- Add hook icon () to mappings legend with custom icon support
|
|
38
|
+
|
|
39
|
+
**New Files:**
|
|
40
|
+
- `lib/dotsync/utils/hook_runner.rb` — HookRunner utility with execute, preview, and template expansion
|
|
41
|
+
- `spec/dotsync/utils/hook_runner_spec.rb` — Comprehensive tests for HookRunner
|
|
42
|
+
|
|
43
|
+
**Documentation:**
|
|
44
|
+
- Add "Post-Sync Hooks" section to README with hook types, examples, template variables, and real-world use cases
|
|
45
|
+
- Add post-sync hooks to Key Features and Table of Contents
|
|
46
|
+
|
|
47
|
+
**Testing:**
|
|
48
|
+
- Add 35 new test examples covering hooks across all layers
|
|
49
|
+
- HookRunner: template expansion, multiple commands, failure handling, shell-escaped paths, preview mode
|
|
50
|
+
- Mapping: hooks attribute, has_hooks?, hook icon display
|
|
51
|
+
- SyncMappings: direction resolution, array concatenation, shorthand hooks, validation of invalid keys
|
|
52
|
+
- PushActionConfig/PullActionConfig: hook extraction, validation of unidirectional constraints
|
|
53
|
+
- PushAction/PullAction: hook execution with changes, skipped without changes, skipped in dry-run
|
|
54
|
+
- Total: 467 examples, 0 failures | Line: 96.45% | Branch: 82.86%
|
|
55
|
+
|
|
1
56
|
## [0.2.2] - 2025-02-07
|
|
2
57
|
|
|
3
58
|
**New Features:**
|
|
@@ -17,7 +72,6 @@
|
|
|
17
72
|
- Add integration tests for glob patterns in FileTransfer (including force mode)
|
|
18
73
|
- Add integration tests for glob patterns in DirectoryDiffer
|
|
19
74
|
- All 432 tests pass with 96.29% line coverage
|
|
20
|
-
|
|
21
75
|
## [0.2.1] - 2025-02-06
|
|
22
76
|
|
|
23
77
|
**Performance Optimizations:**
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -19,6 +19,8 @@ Dotsync is a powerful Ruby gem for managing and synchronizing your dotfiles acro
|
|
|
19
19
|
- **Smart Filtering**: Use `force`, `only`, and `ignore` options to precisely control what gets synced
|
|
20
20
|
- **Automatic Backups**: Pull operations create timestamped backups for easy recovery
|
|
21
21
|
- **Live Watching**: Continuously monitor and sync changes in real-time with `watch` command
|
|
22
|
+
- **Config Includes**: Compose configs from a shared base + machine-specific overlays with `include`
|
|
23
|
+
- **Post-Sync Hooks**: Run commands automatically after files change (e.g., codesigning, chmod, service reload)
|
|
22
24
|
- **Customizable Output**: Control verbosity and customize icons to match your preferences
|
|
23
25
|
- **Auto-Updates**: Get notified when new versions are available
|
|
24
26
|
|
|
@@ -33,6 +35,8 @@ Dotsync is a powerful Ruby gem for managing and synchronizing your dotfiles acro
|
|
|
33
35
|
- [Bidirectional Sync Mappings (Recommended)](#bidirectional-sync-mappings-recommended)
|
|
34
36
|
- [Alternative: Unidirectional Mappings](#alternative-unidirectional-mappings)
|
|
35
37
|
- [Mapping Options (force, only, ignore)](#force-only-and-ignore-options-in-mappings)
|
|
38
|
+
- [Post-Sync Hooks](#post-sync-hooks)
|
|
39
|
+
- [Config Includes](#config-includes)
|
|
36
40
|
- [Safety Features](#safety-features)
|
|
37
41
|
- [Customizing Icons](#customizing-icons)
|
|
38
42
|
- [Automatic Update Checks](#automatic-update-checks)
|
|
@@ -499,6 +503,130 @@ This configuration:
|
|
|
499
503
|
|
|
500
504
|
These options apply when the source is a directory and are relevant for both `push` and `pull` operations.
|
|
501
505
|
|
|
506
|
+
#### Post-Sync Hooks
|
|
507
|
+
|
|
508
|
+
Hooks let you run commands automatically after a mapping's files are transferred. Hooks only execute when files actually changed, and only when using `--apply`.
|
|
509
|
+
|
|
510
|
+
##### Hook Types
|
|
511
|
+
|
|
512
|
+
| Hook | Description | Valid in |
|
|
513
|
+
|------|-------------|----------|
|
|
514
|
+
| `post_sync` | Runs after sync in both directions | `[[sync]]` mappings |
|
|
515
|
+
| `post_push` | Runs only after push | `[[sync]]` and `[[push]]` mappings |
|
|
516
|
+
| `post_pull` | Runs only after pull | `[[sync]]` and `[[pull]]` mappings |
|
|
517
|
+
|
|
518
|
+
For sync mappings, hooks are resolved by direction:
|
|
519
|
+
- **Push**: `post_sync` + `post_push` commands
|
|
520
|
+
- **Pull**: `post_sync` + `post_pull` commands
|
|
521
|
+
|
|
522
|
+
##### Examples
|
|
523
|
+
|
|
524
|
+
**Single command (shorthand mapping):**
|
|
525
|
+
```toml
|
|
526
|
+
[[sync.xdg_bin]]
|
|
527
|
+
force = true
|
|
528
|
+
|
|
529
|
+
[sync.xdg_bin.hooks]
|
|
530
|
+
post_sync = "codesign -s - {files}"
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Multiple commands (explicit sync):**
|
|
534
|
+
```toml
|
|
535
|
+
[[sync.mappings]]
|
|
536
|
+
local = "$XDG_CONFIG_HOME/scripts"
|
|
537
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/scripts"
|
|
538
|
+
|
|
539
|
+
[sync.mappings.hooks]
|
|
540
|
+
post_sync = ["codesign -s - {files}", "chmod 700 {files}"]
|
|
541
|
+
post_pull = "launchctl kickstart -k gui/$(id -u)/com.example.service"
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**Unidirectional mapping:**
|
|
545
|
+
```toml
|
|
546
|
+
[[pull.mappings]]
|
|
547
|
+
src = "$DOTFILES_DIR/scripts"
|
|
548
|
+
dest = "$HOME/Scripts"
|
|
549
|
+
|
|
550
|
+
[pull.mappings.hooks]
|
|
551
|
+
post_pull = ["codesign -s - {files}", "chmod 700 {files}"]
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
##### Template Variables
|
|
555
|
+
|
|
556
|
+
| Variable | Description |
|
|
557
|
+
|----------|-------------|
|
|
558
|
+
| `{files}` | Shell-quoted paths of changed destination files |
|
|
559
|
+
| `{src}` | The mapping's source path |
|
|
560
|
+
| `{dest}` | The mapping's destination path |
|
|
561
|
+
|
|
562
|
+
##### Real-World Examples
|
|
563
|
+
|
|
564
|
+
```toml
|
|
565
|
+
# Codesign scripts after pulling (macOS Ventura+ requirement for LaunchAgents)
|
|
566
|
+
[[sync.xdg_bin]]
|
|
567
|
+
force = true
|
|
568
|
+
|
|
569
|
+
[sync.xdg_bin.hooks]
|
|
570
|
+
post_pull = "codesign -s - {files}"
|
|
571
|
+
|
|
572
|
+
# Reload a LaunchAgent after pulling config changes
|
|
573
|
+
[[pull.mappings]]
|
|
574
|
+
src = "$DOTFILES_DIR/LaunchAgents/com.example.plist"
|
|
575
|
+
dest = "$HOME/Library/LaunchAgents/com.example.plist"
|
|
576
|
+
|
|
577
|
+
[pull.mappings.hooks]
|
|
578
|
+
post_pull = "launchctl kickstart -k gui/$(id -u)/com.example"
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
> [!NOTE]
|
|
582
|
+
> In preview mode (without `--apply`), hooks are displayed as a preview showing what commands would run. Hook failures log errors but do not abort remaining hooks or mappings.
|
|
583
|
+
|
|
584
|
+
#### Config Includes
|
|
585
|
+
|
|
586
|
+
Use `include` to compose a config from a shared base file and a machine-specific overlay. This eliminates duplication when multiple machines share most of the same mappings.
|
|
587
|
+
|
|
588
|
+
```toml
|
|
589
|
+
# dotsync.mbp_personal.toml (overlay — only the delta)
|
|
590
|
+
include = "dotsync.base.toml"
|
|
591
|
+
|
|
592
|
+
# Machine-specific mappings appended to base
|
|
593
|
+
[[sync.xdg_config]]
|
|
594
|
+
path = "claude"
|
|
595
|
+
only = ["settings.json", "instructions", "commands"]
|
|
596
|
+
|
|
597
|
+
[[sync.mappings]]
|
|
598
|
+
local = "$XDG_CONFIG_HOME/opencode/opencode.jsonc"
|
|
599
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/opencode/opencode.mbp_personal.jsonc"
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
```toml
|
|
603
|
+
# dotsync.base.toml (shared across all machines)
|
|
604
|
+
[[sync.home]]
|
|
605
|
+
path = ".zshenv"
|
|
606
|
+
|
|
607
|
+
[[sync.xdg_config]]
|
|
608
|
+
only = ["alacritty", "brewfile", "git", "nvim", "zsh"]
|
|
609
|
+
force = true
|
|
610
|
+
|
|
611
|
+
[watch]
|
|
612
|
+
src = "~/.config"
|
|
613
|
+
paths = ["~/.config/nvim/", "~/.config/zsh/"]
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Merge semantics:**
|
|
617
|
+
|
|
618
|
+
| Type | Behavior | Example |
|
|
619
|
+
|------|----------|---------|
|
|
620
|
+
| Arrays (`[[array-of-tables]]`) | Concatenate (base + overlay) | Base `[[sync.home]]` entries + overlay `[[sync.home]]` entries |
|
|
621
|
+
| Hashes (`[tables]`) | Recursive deep merge (overlay wins on leaves) | Overlay `[watch].dest` overrides base `[watch].dest` |
|
|
622
|
+
| Scalars | Overlay wins | Overlay `force = false` overrides base `force = true` |
|
|
623
|
+
|
|
624
|
+
**Rules:**
|
|
625
|
+
- The `include` path is resolved relative to the overlay file's directory
|
|
626
|
+
- Chained includes (an included file that itself has `include`) are not supported
|
|
627
|
+
- The `include` key is consumed and does not appear in the merged config
|
|
628
|
+
- Cache invalidation tracks both the overlay and included file's mtime/size
|
|
629
|
+
|
|
502
630
|
### Safety Features
|
|
503
631
|
|
|
504
632
|
Dotsync includes several safety mechanisms to prevent accidental data loss:
|
|
@@ -773,21 +901,30 @@ force = true
|
|
|
773
901
|
|
|
774
902
|
### Per-Machine Configuration Files
|
|
775
903
|
|
|
776
|
-
Use
|
|
904
|
+
Use `include` to share a base config across machines, with each machine adding only its delta:
|
|
905
|
+
|
|
906
|
+
```
|
|
907
|
+
dotfiles/xdg_config_home/dotsync/
|
|
908
|
+
dotsync.base.toml # shared mappings, hooks, watch paths
|
|
909
|
+
dotsync.mbp_personal.toml # include + personal-only mappings
|
|
910
|
+
dotsync.mbp_work.toml # include + work-only mappings
|
|
911
|
+
dotsync.mac_mini.toml # include + mac-mini-only mappings
|
|
912
|
+
```
|
|
777
913
|
|
|
778
914
|
```toml
|
|
779
|
-
#
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
915
|
+
# dotsync.mbp_personal.toml — slim overlay
|
|
916
|
+
include = "dotsync.base.toml"
|
|
917
|
+
|
|
918
|
+
[[sync.xdg_config]]
|
|
919
|
+
path = "claude"
|
|
920
|
+
only = ["settings.json", "instructions", "commands"]
|
|
783
921
|
|
|
784
|
-
# In dotsync.work.toml
|
|
785
922
|
[[sync.mappings]]
|
|
786
923
|
local = "$XDG_CONFIG_HOME/dotsync.toml"
|
|
787
|
-
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.
|
|
924
|
+
remote = "$XDG_CONFIG_HOME_MIRROR/dotsync/dotsync.mbp_personal.toml"
|
|
788
925
|
```
|
|
789
926
|
|
|
790
|
-
|
|
927
|
+
Each machine's overlay self-references so `ds push` keeps it in sync with the repo. Use `-c` to select a config:
|
|
791
928
|
```shell
|
|
792
929
|
dotsync -c ~/.config/dotsync/dotsync.macbook.toml push --apply
|
|
793
930
|
```
|
|
@@ -913,21 +1050,31 @@ dotsync -c ~/my-config.toml setup
|
|
|
913
1050
|
|
|
914
1051
|
### Releasing a new version
|
|
915
1052
|
|
|
916
|
-
|
|
917
|
-
2. Add entry to `CHANGELOG.md` documenting changes
|
|
918
|
-
3. Commit all changes: `git add . && git commit -m "Release vX.Y.Z"`
|
|
919
|
-
4. Create annotated tag with changelog extract:
|
|
920
|
-
```shell
|
|
921
|
-
git tag -a vX.Y.Z -m "Release vX.Y.Z
|
|
1053
|
+
**Automated (recommended):**
|
|
922
1054
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1055
|
+
```shell
|
|
1056
|
+
# 1. Update version in lib/dotsync/version.rb
|
|
1057
|
+
# 2. Commit all changes
|
|
1058
|
+
# 3. Run the full release workflow:
|
|
1059
|
+
rake release:publish
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
This generates a CHANGELOG entry from commits, opens it for review, commits, creates an annotated tag (with markdown stripped to plain text), and pushes everything.
|
|
1063
|
+
|
|
1064
|
+
**Individual tasks:**
|
|
1065
|
+
|
|
1066
|
+
```shell
|
|
1067
|
+
rake release:changelog # Generate CHANGELOG entry from commits since last tag
|
|
1068
|
+
rake release:tag # Create annotated tag from CHANGELOG (uses Dotsync::VERSION)
|
|
1069
|
+
rake release:tag[0.3.0] # Create annotated tag for a specific version
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
**Publishing the gem:**
|
|
1073
|
+
|
|
1074
|
+
```shell
|
|
1075
|
+
gem build dotsync.gemspec
|
|
1076
|
+
gem push dotsync-X.Y.Z.gem
|
|
1077
|
+
```
|
|
931
1078
|
|
|
932
1079
|
The `release.yml` GitHub Action automatically creates a GitHub Release when a version tag is pushed, extracting release notes from CHANGELOG.md.
|
|
933
1080
|
|
data/Rakefile
CHANGED
|
@@ -14,14 +14,30 @@ end
|
|
|
14
14
|
|
|
15
15
|
task default: :spec
|
|
16
16
|
|
|
17
|
+
# Strip markdown formatting for plain-text contexts (git tag messages)
|
|
18
|
+
def strip_markdown(text)
|
|
19
|
+
text
|
|
20
|
+
.gsub(/\*\*(.+?)\*\*/, '\1') # **bold** → bold
|
|
21
|
+
.gsub(/`([^`]+)`/, '\1') # `code` → code
|
|
22
|
+
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1') # [text](url) → text
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Load local version.rb, overriding any gem-installed constant
|
|
26
|
+
def local_version
|
|
27
|
+
verbose = $VERBOSE
|
|
28
|
+
$VERBOSE = nil
|
|
29
|
+
load File.expand_path("lib/dotsync/version.rb", __dir__)
|
|
30
|
+
$VERBOSE = verbose
|
|
31
|
+
Dotsync::VERSION
|
|
32
|
+
end
|
|
33
|
+
|
|
17
34
|
namespace :release do
|
|
18
35
|
desc "Generate CHANGELOG entry for a new version"
|
|
19
36
|
# Usage: rake release:changelog[0.2.1]
|
|
20
37
|
task :changelog, [:version] do |_t, args|
|
|
21
38
|
version = args[:version]
|
|
22
39
|
unless version
|
|
23
|
-
|
|
24
|
-
version = Dotsync::VERSION
|
|
40
|
+
version = local_version
|
|
25
41
|
end
|
|
26
42
|
version = version.sub(/^v/, "")
|
|
27
43
|
today = Date.today.strftime("%Y-%m-%d")
|
|
@@ -87,8 +103,7 @@ namespace :release do
|
|
|
87
103
|
task :tag, [:version] do |_t, args|
|
|
88
104
|
version = args[:version]
|
|
89
105
|
unless version
|
|
90
|
-
|
|
91
|
-
version = Dotsync::VERSION
|
|
106
|
+
version = local_version
|
|
92
107
|
end
|
|
93
108
|
version = version.sub(/^v/, "")
|
|
94
109
|
tag_name = "v#{version}"
|
|
@@ -109,11 +124,11 @@ namespace :release do
|
|
|
109
124
|
abort "Version #{version} not found in CHANGELOG.md\nRun 'rake release:changelog[#{version}]' first."
|
|
110
125
|
end
|
|
111
126
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
tag_message = "#{version} - #{date}\n\n#{content}"
|
|
127
|
+
content = strip_markdown(match[2].strip)
|
|
128
|
+
tag_message = "Release v#{version}\n\n#{content}"
|
|
115
129
|
|
|
116
130
|
puts "Tagging commit as #{tag_name}..."
|
|
131
|
+
puts "\n--- Tag message ---\n#{tag_message}\n---\n\n"
|
|
117
132
|
sh "git", "tag", "-a", tag_name, "-m", tag_message
|
|
118
133
|
puts "Tag created. Push with: git push origin #{tag_name}"
|
|
119
134
|
end
|
|
@@ -123,8 +138,7 @@ namespace :release do
|
|
|
123
138
|
task :publish, [:version] do |_t, args|
|
|
124
139
|
version = args[:version]
|
|
125
140
|
unless version
|
|
126
|
-
|
|
127
|
-
version = Dotsync::VERSION
|
|
141
|
+
version = local_version
|
|
128
142
|
end
|
|
129
143
|
version = version.sub(/^v/, "")
|
|
130
144
|
|
|
@@ -23,8 +23,9 @@ module Dotsync
|
|
|
23
23
|
|
|
24
24
|
MAPPINGS_LEGEND = [
|
|
25
25
|
[Icons.force, "The source will overwrite the destination"],
|
|
26
|
-
[Icons.only, "
|
|
27
|
-
[Icons.ignore, "
|
|
26
|
+
[Icons.only, "Filtered by 'only' whitelist"],
|
|
27
|
+
[Icons.ignore, "Filtered by 'ignore' blacklist"],
|
|
28
|
+
[Icons.hook, "Post-sync hooks configured"],
|
|
28
29
|
[Icons.invalid, "Invalid paths detected in the source or destination"]
|
|
29
30
|
]
|
|
30
31
|
|
|
@@ -135,6 +136,42 @@ module Dotsync
|
|
|
135
136
|
end
|
|
136
137
|
end
|
|
137
138
|
|
|
139
|
+
def execute_hooks
|
|
140
|
+
valid_mappings.each_with_index do |mapping, idx|
|
|
141
|
+
next unless mapping.has_hooks?
|
|
142
|
+
|
|
143
|
+
differ = differs[idx]
|
|
144
|
+
changed_files = differ.additions + differ.modifications
|
|
145
|
+
next if changed_files.empty?
|
|
146
|
+
|
|
147
|
+
runner = Dotsync::HookRunner.new(mapping: mapping, changed_files: changed_files, logger: logger)
|
|
148
|
+
runner.execute
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def show_hooks_preview
|
|
153
|
+
hooks_to_run = []
|
|
154
|
+
|
|
155
|
+
valid_mappings.each_with_index do |mapping, idx|
|
|
156
|
+
next unless mapping.has_hooks?
|
|
157
|
+
|
|
158
|
+
differ = differs[idx]
|
|
159
|
+
changed_files = differ.additions + differ.modifications
|
|
160
|
+
next if changed_files.empty?
|
|
161
|
+
|
|
162
|
+
runner = Dotsync::HookRunner.new(mapping: mapping, changed_files: changed_files, logger: logger)
|
|
163
|
+
hooks_to_run.concat(runner.preview)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
return if hooks_to_run.empty?
|
|
167
|
+
|
|
168
|
+
info("Hooks to run:", icon: :hook)
|
|
169
|
+
hooks_to_run.each do |command|
|
|
170
|
+
logger.log(" #{command}")
|
|
171
|
+
end
|
|
172
|
+
logger.log("")
|
|
173
|
+
end
|
|
174
|
+
|
|
138
175
|
private
|
|
139
176
|
# Computes diffs for all valid mappings.
|
|
140
177
|
#
|
|
@@ -16,6 +16,7 @@ module Dotsync
|
|
|
16
16
|
show_mappings if output_sections[:mappings]
|
|
17
17
|
show_differences_legend if has_differences? && output_sections[:differences_legend]
|
|
18
18
|
show_differences(diff_content: output_sections[:diff_content]) if output_sections[:differences]
|
|
19
|
+
show_hooks_preview if output_sections[:differences]
|
|
19
20
|
|
|
20
21
|
return unless options[:apply]
|
|
21
22
|
|
|
@@ -32,6 +33,7 @@ module Dotsync
|
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
transfer_mappings
|
|
36
|
+
execute_hooks
|
|
35
37
|
action("Mappings pulled", icon: :done)
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -14,6 +14,7 @@ module Dotsync
|
|
|
14
14
|
show_mappings if output_sections[:mappings]
|
|
15
15
|
show_differences_legend if has_differences? && output_sections[:differences_legend]
|
|
16
16
|
show_differences(diff_content: output_sections[:diff_content]) if output_sections[:differences]
|
|
17
|
+
show_hooks_preview if output_sections[:differences]
|
|
17
18
|
|
|
18
19
|
return unless options[:apply]
|
|
19
20
|
|
|
@@ -23,6 +24,7 @@ module Dotsync
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
transfer_mappings
|
|
27
|
+
execute_hooks
|
|
26
28
|
action("Mappings pushed", icon: :done)
|
|
27
29
|
end
|
|
28
30
|
end
|
|
@@ -96,6 +96,12 @@ module Dotsync
|
|
|
96
96
|
base["ignore"] = mapping["ignore"] if mapping.key?("ignore")
|
|
97
97
|
base["only"] = mapping["only"] if mapping.key?("only")
|
|
98
98
|
|
|
99
|
+
# Resolve hooks for direction
|
|
100
|
+
if mapping.key?("hooks")
|
|
101
|
+
resolved = resolve_hooks_for_direction(mapping["hooks"], direction)
|
|
102
|
+
base["hooks"] = resolved if resolved.any?
|
|
103
|
+
end
|
|
104
|
+
|
|
99
105
|
base
|
|
100
106
|
end
|
|
101
107
|
|
|
@@ -142,9 +148,41 @@ module Dotsync
|
|
|
142
148
|
base["ignore"] = mapping["ignore"] if mapping.key?("ignore")
|
|
143
149
|
base["only"] = only if only
|
|
144
150
|
|
|
151
|
+
# Resolve hooks for direction
|
|
152
|
+
if mapping.key?("hooks")
|
|
153
|
+
resolved = resolve_hooks_for_direction(mapping["hooks"], direction)
|
|
154
|
+
base["hooks"] = resolved if resolved.any?
|
|
155
|
+
end
|
|
156
|
+
|
|
145
157
|
base
|
|
146
158
|
end
|
|
147
159
|
|
|
160
|
+
def resolve_hooks_for_direction(raw_hooks, direction)
|
|
161
|
+
return [] unless raw_hooks.is_a?(Hash)
|
|
162
|
+
|
|
163
|
+
hooks = Array(raw_hooks["post_sync"])
|
|
164
|
+
case direction
|
|
165
|
+
when :push
|
|
166
|
+
hooks += Array(raw_hooks["post_push"])
|
|
167
|
+
when :pull
|
|
168
|
+
hooks += Array(raw_hooks["post_pull"])
|
|
169
|
+
end
|
|
170
|
+
hooks
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def validate_hooks!(hooks, context)
|
|
174
|
+
return unless hooks.is_a?(Hash)
|
|
175
|
+
|
|
176
|
+
valid_keys = %w[post_sync post_push post_pull]
|
|
177
|
+
invalid_keys = hooks.keys - valid_keys
|
|
178
|
+
return if invalid_keys.empty?
|
|
179
|
+
|
|
180
|
+
raise Dotsync::ConfigError,
|
|
181
|
+
"Configuration error in #{context}: " \
|
|
182
|
+
"Invalid hook key(s): #{invalid_keys.join(", ")}. " \
|
|
183
|
+
"Valid keys are: #{valid_keys.join(", ")}"
|
|
184
|
+
end
|
|
185
|
+
|
|
148
186
|
def build_path(base, path)
|
|
149
187
|
path ? File.join(base, path) : base
|
|
150
188
|
end
|
|
@@ -171,6 +209,8 @@ module Dotsync
|
|
|
171
209
|
"Configuration error in sync.mappings ##{index + 1}: " \
|
|
172
210
|
"Each mapping must have 'local' and 'remote' keys."
|
|
173
211
|
end
|
|
212
|
+
|
|
213
|
+
validate_hooks!(mapping["hooks"], "sync.mappings ##{index + 1}") if mapping.key?("hooks")
|
|
174
214
|
end
|
|
175
215
|
end
|
|
176
216
|
|
|
@@ -184,6 +224,8 @@ module Dotsync
|
|
|
184
224
|
"Configuration error in sync.#{shorthand_type} ##{index + 1}: " \
|
|
185
225
|
"Each mapping must be a table."
|
|
186
226
|
end
|
|
227
|
+
|
|
228
|
+
validate_hooks!(mapping["hooks"], "sync.#{shorthand_type} ##{index + 1}") if mapping.is_a?(Hash) && mapping.key?("hooks")
|
|
187
229
|
end
|
|
188
230
|
end
|
|
189
231
|
end
|
|
@@ -22,7 +22,15 @@ module Dotsync
|
|
|
22
22
|
|
|
23
23
|
def section_mappings
|
|
24
24
|
return [] unless section && section["mappings"]
|
|
25
|
-
Array(section["mappings"]).map
|
|
25
|
+
Array(section["mappings"]).map do |mapping|
|
|
26
|
+
attrs = mapping.dup
|
|
27
|
+
if attrs.key?("hooks") && attrs["hooks"].is_a?(Hash)
|
|
28
|
+
resolved = Array(attrs["hooks"]["post_pull"])
|
|
29
|
+
attrs["hooks"] = resolved.any? ? resolved : nil
|
|
30
|
+
attrs.delete("hooks") unless attrs["hooks"]
|
|
31
|
+
end
|
|
32
|
+
Dotsync::Mapping.new(attrs)
|
|
33
|
+
end
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
def validate!
|
|
@@ -46,6 +54,16 @@ module Dotsync
|
|
|
46
54
|
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
47
55
|
raise "Configuration error in pull mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
48
56
|
end
|
|
57
|
+
|
|
58
|
+
if mapping.is_a?(Hash) && mapping.key?("hooks") && mapping["hooks"].is_a?(Hash)
|
|
59
|
+
invalid_keys = mapping["hooks"].keys - ["post_pull"]
|
|
60
|
+
if invalid_keys.any?
|
|
61
|
+
raise Dotsync::ConfigError,
|
|
62
|
+
"Configuration error in pull mapping ##{index + 1}: " \
|
|
63
|
+
"Only 'post_pull' hooks are allowed in [pull] mappings. " \
|
|
64
|
+
"Invalid key(s): #{invalid_keys.join(", ")}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
49
67
|
end
|
|
50
68
|
end
|
|
51
69
|
end
|
|
@@ -17,7 +17,15 @@ module Dotsync
|
|
|
17
17
|
|
|
18
18
|
def section_mappings
|
|
19
19
|
return [] unless section && section["mappings"]
|
|
20
|
-
Array(section["mappings"]).map
|
|
20
|
+
Array(section["mappings"]).map do |mapping|
|
|
21
|
+
attrs = mapping.dup
|
|
22
|
+
if attrs.key?("hooks") && attrs["hooks"].is_a?(Hash)
|
|
23
|
+
resolved = Array(attrs["hooks"]["post_push"])
|
|
24
|
+
attrs["hooks"] = resolved.any? ? resolved : nil
|
|
25
|
+
attrs.delete("hooks") unless attrs["hooks"]
|
|
26
|
+
end
|
|
27
|
+
Dotsync::Mapping.new(attrs)
|
|
28
|
+
end
|
|
21
29
|
end
|
|
22
30
|
|
|
23
31
|
def validate!
|
|
@@ -41,6 +49,16 @@ module Dotsync
|
|
|
41
49
|
unless mapping.is_a?(Hash) && mapping.key?("src") && mapping.key?("dest")
|
|
42
50
|
raise "Configuration error in push mapping ##{index + 1}: Each mapping must have 'src' and 'dest' keys."
|
|
43
51
|
end
|
|
52
|
+
|
|
53
|
+
if mapping.is_a?(Hash) && mapping.key?("hooks") && mapping["hooks"].is_a?(Hash)
|
|
54
|
+
invalid_keys = mapping["hooks"].keys - ["post_push"]
|
|
55
|
+
if invalid_keys.any?
|
|
56
|
+
raise Dotsync::ConfigError,
|
|
57
|
+
"Configuration error in push mapping ##{index + 1}: " \
|
|
58
|
+
"Only 'post_push' hooks are allowed in [push] mappings. " \
|
|
59
|
+
"Invalid key(s): #{invalid_keys.join(", ")}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
44
62
|
end
|
|
45
63
|
end
|
|
46
64
|
end
|
data/lib/dotsync/errors.rb
CHANGED
data/lib/dotsync/icons.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Dotsync
|
|
|
18
18
|
DEFAULT_ONLY = " "
|
|
19
19
|
DEFAULT_IGNORE = " "
|
|
20
20
|
DEFAULT_INVALID = " "
|
|
21
|
+
DEFAULT_HOOK = " "
|
|
21
22
|
|
|
22
23
|
# Default Mappings Differences icons
|
|
23
24
|
DEFAULT_DIFF_CREATED = " "
|
|
@@ -50,6 +51,7 @@ module Dotsync
|
|
|
50
51
|
only: config.dig("icons", "only") || DEFAULT_ONLY,
|
|
51
52
|
ignore: config.dig("icons", "ignore") || DEFAULT_IGNORE,
|
|
52
53
|
invalid: config.dig("icons", "invalid") || DEFAULT_INVALID,
|
|
54
|
+
hook: config.dig("icons", "hook") || DEFAULT_HOOK,
|
|
53
55
|
# Differences Legend
|
|
54
56
|
diff_created: config.dig("icons", "diff_created") || DEFAULT_DIFF_CREATED,
|
|
55
57
|
diff_updated: config.dig("icons", "diff_updated") || DEFAULT_DIFF_UPDATED,
|
|
@@ -75,6 +77,10 @@ module Dotsync
|
|
|
75
77
|
@custom_icons[:invalid] || DEFAULT_INVALID
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
def self.hook
|
|
81
|
+
@custom_icons[:hook] || DEFAULT_HOOK
|
|
82
|
+
end
|
|
83
|
+
|
|
78
84
|
# Differences Legend methods
|
|
79
85
|
|
|
80
86
|
def self.diff_created
|
|
@@ -100,6 +106,7 @@ module Dotsync
|
|
|
100
106
|
diff: DIFF,
|
|
101
107
|
force: -> { force },
|
|
102
108
|
ignore: -> { ignore },
|
|
109
|
+
hook: DEFAULT_HOOK,
|
|
103
110
|
pull: PULL,
|
|
104
111
|
push: PUSH,
|
|
105
112
|
watch: WATCH,
|
|
@@ -13,6 +13,7 @@ require_relative "../utils/directory_differ"
|
|
|
13
13
|
require_relative "../utils/config_cache"
|
|
14
14
|
require_relative "../utils/content_diff"
|
|
15
15
|
require_relative "../utils/parallel"
|
|
16
|
+
require_relative "../utils/hook_runner"
|
|
16
17
|
|
|
17
18
|
# Models
|
|
18
19
|
require_relative "../models/mapping"
|
|
@@ -13,6 +13,7 @@ require_relative "../utils/directory_differ"
|
|
|
13
13
|
require_relative "../utils/config_cache"
|
|
14
14
|
require_relative "../utils/content_diff"
|
|
15
15
|
require_relative "../utils/parallel"
|
|
16
|
+
require_relative "../utils/hook_runner"
|
|
16
17
|
|
|
17
18
|
# Models
|
|
18
19
|
require_relative "../models/mapping"
|
|
@@ -29,6 +29,7 @@ module Dotsync
|
|
|
29
29
|
@original_ignores = Array(attributes["ignore"])
|
|
30
30
|
@original_only = Array(attributes["only"])
|
|
31
31
|
@force = attributes["force"] || false
|
|
32
|
+
@hooks = Array(attributes["hooks"])
|
|
32
33
|
|
|
33
34
|
@sanitized_src, @sanitized_dest, @sanitized_ignores, @sanitized_only = process_paths(
|
|
34
35
|
@original_src,
|
|
@@ -58,6 +59,12 @@ module Dotsync
|
|
|
58
59
|
@force
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
attr_reader :hooks
|
|
63
|
+
|
|
64
|
+
def has_hooks?
|
|
65
|
+
@hooks.any?
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
def directories?
|
|
62
69
|
File.directory?(src) && File.directory?(dest)
|
|
63
70
|
end
|
|
@@ -103,6 +110,7 @@ module Dotsync
|
|
|
103
110
|
msg << Icons.force if force?
|
|
104
111
|
msg << Icons.only if has_inclusions?
|
|
105
112
|
msg << Icons.ignore if has_ignores?
|
|
113
|
+
msg << Icons.hook if has_hooks?
|
|
106
114
|
msg << Icons.invalid unless valid?
|
|
107
115
|
msg.join
|
|
108
116
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "digest"
|
|
5
|
+
require_relative "config_merger"
|
|
5
6
|
|
|
6
7
|
module Dotsync
|
|
7
8
|
class ConfigCache
|
|
@@ -19,7 +20,7 @@ module Dotsync
|
|
|
19
20
|
|
|
20
21
|
def load
|
|
21
22
|
# Skip cache if disabled via environment variable
|
|
22
|
-
return
|
|
23
|
+
return resolve_config if ENV["DOTSYNC_NO_CACHE"]
|
|
23
24
|
|
|
24
25
|
return parse_and_cache unless valid_cache?
|
|
25
26
|
|
|
@@ -47,6 +48,15 @@ module Dotsync
|
|
|
47
48
|
cache_age_days = (Time.now.to_f - meta["cached_at"]) / 86400
|
|
48
49
|
return false if cache_age_days > 7
|
|
49
50
|
|
|
51
|
+
# Check include file validity if present
|
|
52
|
+
if meta["include_path"]
|
|
53
|
+
return false unless File.exist?(meta["include_path"])
|
|
54
|
+
|
|
55
|
+
include_stat = File.stat(meta["include_path"])
|
|
56
|
+
return false if include_stat.mtime.to_f != meta["include_mtime"]
|
|
57
|
+
return false if include_stat.size != meta["include_size"]
|
|
58
|
+
end
|
|
59
|
+
|
|
50
60
|
true
|
|
51
61
|
rescue StandardError
|
|
52
62
|
# Any error in validation means invalid cache
|
|
@@ -54,7 +64,7 @@ module Dotsync
|
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
def parse_and_cache
|
|
57
|
-
config =
|
|
67
|
+
config = resolve_config
|
|
58
68
|
|
|
59
69
|
# Write cache files
|
|
60
70
|
FileUtils.mkdir_p(@cache_dir)
|
|
@@ -67,6 +77,12 @@ module Dotsync
|
|
|
67
77
|
config
|
|
68
78
|
end
|
|
69
79
|
|
|
80
|
+
def resolve_config
|
|
81
|
+
raw = parse_toml
|
|
82
|
+
@merger = ConfigMerger.new(raw, @config_path)
|
|
83
|
+
@merger.resolve
|
|
84
|
+
end
|
|
85
|
+
|
|
70
86
|
def parse_toml
|
|
71
87
|
require "toml-rb"
|
|
72
88
|
TomlRB.load_file(@config_path)
|
|
@@ -74,13 +90,22 @@ module Dotsync
|
|
|
74
90
|
|
|
75
91
|
def build_metadata
|
|
76
92
|
source_stat = File.stat(@config_path)
|
|
77
|
-
{
|
|
93
|
+
meta = {
|
|
78
94
|
source_path: @config_path,
|
|
79
95
|
source_size: source_stat.size,
|
|
80
96
|
source_mtime: source_stat.mtime.to_f,
|
|
81
97
|
cached_at: Time.now.to_f,
|
|
82
98
|
dotsync_version: Dotsync::VERSION
|
|
83
99
|
}
|
|
100
|
+
|
|
101
|
+
if @merger&.include_path
|
|
102
|
+
include_stat = File.stat(@merger.include_path)
|
|
103
|
+
meta[:include_path] = @merger.include_path
|
|
104
|
+
meta[:include_mtime] = include_stat.mtime.to_f
|
|
105
|
+
meta[:include_size] = include_stat.size
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
meta
|
|
84
109
|
end
|
|
85
110
|
end
|
|
86
111
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "toml-rb"
|
|
4
|
+
|
|
5
|
+
module Dotsync
|
|
6
|
+
class ConfigMerger
|
|
7
|
+
attr_reader :include_path
|
|
8
|
+
|
|
9
|
+
def self.resolve(config_hash, config_path)
|
|
10
|
+
new(config_hash, config_path).resolve
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(config_hash, config_path)
|
|
14
|
+
@config = config_hash
|
|
15
|
+
@config_path = config_path
|
|
16
|
+
@include_path = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def resolve
|
|
20
|
+
return @config unless @config.key?("include")
|
|
21
|
+
|
|
22
|
+
validate_include_value!
|
|
23
|
+
@include_path = resolve_include_path
|
|
24
|
+
validate_include_exists!
|
|
25
|
+
|
|
26
|
+
base_config = load_base_config
|
|
27
|
+
validate_no_chained_includes!(base_config)
|
|
28
|
+
|
|
29
|
+
merged = deep_merge(base_config, overlay)
|
|
30
|
+
merged
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
def validate_include_value!
|
|
35
|
+
unless @config["include"].is_a?(String)
|
|
36
|
+
raise ConfigError, "Config Error: 'include' must be a string path, got #{@config["include"].class}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resolve_include_path
|
|
41
|
+
include_value = @config["include"]
|
|
42
|
+
config_dir = File.dirname(@config_path)
|
|
43
|
+
File.expand_path(include_value, config_dir)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_include_exists!
|
|
47
|
+
unless File.exist?(@include_path)
|
|
48
|
+
raise ConfigError, "Config Error: Included file not found: #{@include_path}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def load_base_config
|
|
53
|
+
TomlRB.load_file(@include_path)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_no_chained_includes!(base_config)
|
|
57
|
+
if base_config.key?("include")
|
|
58
|
+
raise ConfigError, "Config Error: Chained includes are not supported (found 'include' in #{@include_path})"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def overlay
|
|
63
|
+
@config.reject { |key, _| key == "include" }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def deep_merge(base, overlay)
|
|
67
|
+
base.merge(overlay) do |_key, base_val, overlay_val|
|
|
68
|
+
if base_val.is_a?(Hash) && overlay_val.is_a?(Hash)
|
|
69
|
+
deep_merge(base_val, overlay_val)
|
|
70
|
+
elsif base_val.is_a?(Array) && overlay_val.is_a?(Array)
|
|
71
|
+
base_val + overlay_val
|
|
72
|
+
else
|
|
73
|
+
overlay_val
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "shellwords"
|
|
5
|
+
|
|
6
|
+
module Dotsync
|
|
7
|
+
class HookRunner
|
|
8
|
+
def initialize(mapping:, changed_files:, logger:)
|
|
9
|
+
@mapping = mapping
|
|
10
|
+
@changed_files = changed_files
|
|
11
|
+
@logger = logger
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
@mapping.hooks.map do |command|
|
|
16
|
+
expanded = expand_template(command)
|
|
17
|
+
run_command(expanded)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def preview
|
|
22
|
+
@mapping.hooks.map { |command| expand_template(command) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def expand_template(command)
|
|
27
|
+
files_str = @changed_files.map { |f| Shellwords.escape(f) }.join(" ")
|
|
28
|
+
|
|
29
|
+
command
|
|
30
|
+
.gsub("{files}", files_str)
|
|
31
|
+
.gsub("{src}", @mapping.src)
|
|
32
|
+
.gsub("{dest}", @mapping.dest)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run_command(command)
|
|
36
|
+
stdout, stderr, status = Open3.capture3(command)
|
|
37
|
+
|
|
38
|
+
if status.success?
|
|
39
|
+
@logger.info("Hook succeeded: #{command}", icon: :hook)
|
|
40
|
+
else
|
|
41
|
+
@logger.error("Hook failed: #{command}")
|
|
42
|
+
@logger.error(" #{stderr.strip}") unless stderr.strip.empty?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
{ command: command, stdout: stdout, stderr: stderr, success: status.success? }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/dotsync/version.rb
CHANGED
data/lib/dotsync.rb
CHANGED
|
@@ -31,7 +31,9 @@ require_relative "dotsync/utils/file_transfer"
|
|
|
31
31
|
require_relative "dotsync/utils/directory_differ"
|
|
32
32
|
require_relative "dotsync/utils/version_checker"
|
|
33
33
|
require_relative "dotsync/utils/config_cache"
|
|
34
|
+
require_relative "dotsync/utils/config_merger"
|
|
34
35
|
require_relative "dotsync/utils/parallel"
|
|
36
|
+
require_relative "dotsync/utils/hook_runner"
|
|
35
37
|
|
|
36
38
|
# Models
|
|
37
39
|
require_relative "dotsync/models/mapping"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dotsync
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Sáenz
|
|
@@ -334,9 +334,11 @@ files:
|
|
|
334
334
|
- lib/dotsync/runner.rb
|
|
335
335
|
- lib/dotsync/tasks/actions.rake
|
|
336
336
|
- lib/dotsync/utils/config_cache.rb
|
|
337
|
+
- lib/dotsync/utils/config_merger.rb
|
|
337
338
|
- lib/dotsync/utils/content_diff.rb
|
|
338
339
|
- lib/dotsync/utils/directory_differ.rb
|
|
339
340
|
- lib/dotsync/utils/file_transfer.rb
|
|
341
|
+
- lib/dotsync/utils/hook_runner.rb
|
|
340
342
|
- lib/dotsync/utils/logger.rb
|
|
341
343
|
- lib/dotsync/utils/parallel.rb
|
|
342
344
|
- lib/dotsync/utils/path_utils.rb
|