yobi 0.3.1 → 1.1.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/.ruby-version +1 -0
- data/CHANGELOG.md +17 -0
- data/README.md +83 -44
- data/lib/yobi/argv_builder.rb +1 -35
- data/lib/yobi/cancellable_proxy.rb +48 -0
- data/lib/yobi/cancellation.rb +97 -0
- data/lib/yobi/errors.rb +45 -30
- data/lib/yobi/fancy_hash.rb +1 -5
- data/lib/yobi/io_handle.rb +9 -17
- data/lib/yobi/mount_handle.rb +7 -11
- data/lib/yobi/repository/backup.rb +59 -90
- data/lib/yobi/repository/cat.rb +23 -43
- data/lib/yobi/repository/check.rb +21 -34
- data/lib/yobi/repository/copy.rb +11 -13
- data/lib/yobi/repository/diff.rb +18 -49
- data/lib/yobi/repository/dump.rb +14 -15
- data/lib/yobi/repository/find.rb +17 -37
- data/lib/yobi/repository/forget.rb +35 -39
- data/lib/yobi/repository/init.rb +19 -24
- data/lib/yobi/repository/key.rb +18 -33
- data/lib/yobi/repository/list.rb +5 -6
- data/lib/yobi/repository/ls.rb +20 -43
- data/lib/yobi/repository/migrate.rb +6 -6
- data/lib/yobi/repository/mount.rb +22 -32
- data/lib/yobi/repository/prune.rb +11 -9
- data/lib/yobi/repository/recover.rb +2 -4
- data/lib/yobi/repository/repair.rb +16 -23
- data/lib/yobi/repository/restore.rb +31 -54
- data/lib/yobi/repository/rewrite.rb +15 -20
- data/lib/yobi/repository/snapshots.rb +7 -8
- data/lib/yobi/repository/stats.rb +26 -21
- data/lib/yobi/repository/tag.rb +23 -33
- data/lib/yobi/repository/unlock.rb +2 -4
- data/lib/yobi/repository.rb +96 -25
- data/lib/yobi/restic.rb +93 -114
- data/lib/yobi/restic_output.rb +2 -74
- data/lib/yobi/snapshot.rb +15 -27
- data/lib/yobi/version.rb +1 -2
- data/lib/yobi.rb +2 -0
- data/sig/yobi.rbs +83 -6
- metadata +5 -2
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
5
|
+
# +restic repair index+: creates a new index based on the pack files
|
|
6
6
|
# present in this repository. Successor to the deprecated
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# @param read_all_packs [Boolean] read every pack file fully instead of just headers
|
|
10
|
-
# @return [true]
|
|
7
|
+
# +rebuild-index+ command. +read_all_packs: true+ reads every pack file
|
|
8
|
+
# fully instead of just headers.
|
|
11
9
|
def repair_index(read_all_packs: false)
|
|
12
10
|
argv = build_argv("repair", "index") do |a|
|
|
13
11
|
a.flag(:read_all_packs) if read_all_packs
|
|
@@ -16,34 +14,29 @@ module Yobi
|
|
|
16
14
|
true
|
|
17
15
|
end
|
|
18
16
|
|
|
19
|
-
#
|
|
20
|
-
#
|
|
17
|
+
# +restic repair packs+: extracts intact blobs from the given pack
|
|
18
|
+
# +ids:+, rebuilds the index to drop the damaged packs, and removes
|
|
21
19
|
# them from the repository. Restic also writes a backup copy of each
|
|
22
|
-
# given pack file (named
|
|
20
|
+
# given pack file (named +pack-<id>+) into the calling process's
|
|
23
21
|
# current working directory before removing it. There's no flag to
|
|
24
22
|
# disable this.
|
|
25
|
-
#
|
|
26
|
-
# @param ids [Array<String>, String] pack IDs
|
|
27
|
-
# @return [true]
|
|
28
23
|
def repair_packs(ids:)
|
|
29
24
|
argv = build_argv("repair", "packs", ids)
|
|
30
25
|
run_restic(argv)
|
|
31
26
|
true
|
|
32
27
|
end
|
|
33
28
|
|
|
34
|
-
#
|
|
35
|
-
# new ones with damaged directories/file contents removed. This
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# Depends on a correct index
|
|
29
|
+
# +restic repair snapshots+: scans the given snapshots and generates
|
|
30
|
+
# new ones with damaged directories/file contents removed. This causes
|
|
31
|
+
# data loss for the content actually removed; prefer a fresh
|
|
32
|
+
# Yobi::Repository#backup where the source data is still available.
|
|
33
|
+
# Depends on a correct index - call #repair_index first.
|
|
39
34
|
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# @param tags [Array<String>, String] filter by tag(s)
|
|
46
|
-
# @return [true]
|
|
35
|
+
# +snapshot_ids:+ names the snapshots to repair, or all of them if
|
|
36
|
+
# empty. +dry_run:+ reports what would happen without doing it.
|
|
37
|
+
# +forget:+ removes the original damaged snapshots after repairing.
|
|
38
|
+
# +hosts:+, +paths:+, and +tags:+ each accept a single value or an
|
|
39
|
+
# Array to filter by.
|
|
47
40
|
def repair_snapshots(snapshot_ids: [], dry_run: false, forget: false, hosts: [], paths: [], tags: [])
|
|
48
41
|
argv = build_argv("repair", "snapshots", snapshot_ids) do |a|
|
|
49
42
|
a.flag(:dry_run) if dry_run
|
|
@@ -2,32 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
5
|
+
# +restic restore+: extracts a snapshot's contents to a target directory.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# @param verbose [Boolean] stream a {Yobi::RestoreVerboseStatus} per file to the block
|
|
28
|
-
# @param verify [Boolean] verify restored file content against the repository
|
|
29
|
-
# @yieldparam message [Yobi::RestoreStatus, Yobi::RestoreVerboseStatus]
|
|
30
|
-
# @return [Yobi::RestoreOutcome]
|
|
7
|
+
# +snapshot_id:+ is a snapshot ID or +"latest"+. +target:+ is the
|
|
8
|
+
# directory to restore into.
|
|
9
|
+
#
|
|
10
|
+
# +excludes:+, +exclude_files:+, +exclude_xattrs:+, +includes:+,
|
|
11
|
+
# +include_files:+, +include_xattrs:+ and their +i+-prefixed
|
|
12
|
+
# (case-insensitive) variants each accept a single value or an Array.
|
|
13
|
+
#
|
|
14
|
+
# +hosts:+, +paths:+ and +tags:+ each accept a single value or an
|
|
15
|
+
# Array and are only relevant when +snapshot_id+ is +"latest"+.
|
|
16
|
+
#
|
|
17
|
+
# +delete:+ deletes files in +target+ not present in the snapshot.
|
|
18
|
+
# +dry_run:+ reports what would happen without doing it.
|
|
19
|
+
# +overwrite:+ is +"always"+, +"if-changed"+, or +"if-newer"+.
|
|
20
|
+
# +ownership_by_name:+ maps ownership by user/group name instead of
|
|
21
|
+
# numeric ID. +sparse:+ writes sparse files. +verify:+ verifies restored
|
|
22
|
+
# file content against the repository.
|
|
23
|
+
#
|
|
24
|
+
# When +verbose: true+, a Yobi::RestoreVerboseStatus is streamed to the
|
|
25
|
+
# block per file. The block also receives Yobi::RestoreStatus messages
|
|
26
|
+
# as they arrive. Returns a Yobi::RestoreOutcome.
|
|
31
27
|
def restore(snapshot_id:, target:, excludes: [], exclude_files: [], exclude_xattrs: [], hosts: [],
|
|
32
28
|
iexcludes: [], iexclude_files: [], iincludes: [], iinclude_files: [], includes: [], include_files: [],
|
|
33
29
|
include_xattrs: [], paths: [], tags: [], delete: false, dry_run: false, overwrite: nil,
|
|
@@ -58,7 +54,7 @@ module Yobi
|
|
|
58
54
|
|
|
59
55
|
output = Yobi::ResticOutput.new(transform: Yobi::RestoreMessageWrapper)
|
|
60
56
|
# Restic's own restore summary isn't meant for the live block - only
|
|
61
|
-
#
|
|
57
|
+
# RestoreOutcome#summary's post-hoc access sees it - but it shares
|
|
62
58
|
# the same @transform (needed there since it isn't a status/verbose_status
|
|
63
59
|
# message), so it has to be filtered back out here instead.
|
|
64
60
|
live_block = block && proc { |message| block.call(message) if message.is_a?(RestoreStatus) || message.is_a?(RestoreVerboseStatus) }
|
|
@@ -67,16 +63,7 @@ module Yobi
|
|
|
67
63
|
end
|
|
68
64
|
end
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
# it in the matching typed class. Used both by {Yobi::Repository#restore}
|
|
72
|
-
# (as the {Yobi::ResticOutput} `transform:` for a live streaming run) and
|
|
73
|
-
# by {Yobi::RestoreOutcome}'s own post-hoc accessors.
|
|
74
|
-
#
|
|
75
|
-
# @private
|
|
76
|
-
module RestoreMessageWrapper
|
|
77
|
-
# @param raw [Hash]
|
|
78
|
-
# @return [Yobi::RestoreStatus, Yobi::RestoreVerboseStatus, Hash]
|
|
79
|
-
# the raw Hash itself for a message_type this version of Yobi doesn't recognize
|
|
66
|
+
module RestoreMessageWrapper # :nodoc:
|
|
80
67
|
def self.call(raw)
|
|
81
68
|
case raw["message_type"]
|
|
82
69
|
when "status" then RestoreStatus.new(raw)
|
|
@@ -86,23 +73,21 @@ module Yobi
|
|
|
86
73
|
end
|
|
87
74
|
end
|
|
88
75
|
|
|
89
|
-
# The outcome of one
|
|
76
|
+
# The outcome of one Yobi::Repository#restore call.
|
|
90
77
|
class RestoreOutcome
|
|
91
|
-
#
|
|
78
|
+
# The Yobi::ResticOutput backing this outcome.
|
|
92
79
|
attr_reader :output
|
|
93
80
|
|
|
94
|
-
#
|
|
95
|
-
def initialize(execution)
|
|
81
|
+
def initialize(execution) # :nodoc:
|
|
96
82
|
@output = execution[:output]
|
|
97
83
|
end
|
|
98
84
|
|
|
99
|
-
#
|
|
85
|
+
# Restic's own +"summary"+ fields (+"total_files"+, +"files_restored"+, ...).
|
|
100
86
|
def summary
|
|
101
87
|
@summary ||= output.messages("summary").first || {}
|
|
102
88
|
end
|
|
103
89
|
alias_method :report, :summary
|
|
104
90
|
|
|
105
|
-
# @return [void]
|
|
106
91
|
def pretty_print(q)
|
|
107
92
|
q.object_group(self) do
|
|
108
93
|
q.breakable
|
|
@@ -111,49 +96,41 @@ module Yobi
|
|
|
111
96
|
end
|
|
112
97
|
end
|
|
113
98
|
|
|
114
|
-
# One
|
|
115
|
-
# https://restic.readthedocs.io/en/stable/075_scripting.html#restore
|
|
99
|
+
# One +"status"+ message from a live restore run. See
|
|
100
|
+
# https://restic.readthedocs.io/en/stable/075_scripting.html#restore.
|
|
116
101
|
class RestoreStatus < Yobi::FancyHash
|
|
117
|
-
# @return [Float, nil]
|
|
118
102
|
def percent_done
|
|
119
103
|
self["percent_done"]
|
|
120
104
|
end
|
|
121
105
|
|
|
122
|
-
# @return [Integer, nil]
|
|
123
106
|
def total_files
|
|
124
107
|
self["total_files"]
|
|
125
108
|
end
|
|
126
109
|
|
|
127
|
-
# @return [Integer, nil]
|
|
128
110
|
def files_restored
|
|
129
111
|
self["files_restored"]
|
|
130
112
|
end
|
|
131
113
|
|
|
132
|
-
# @return [Integer, nil]
|
|
133
114
|
def total_bytes
|
|
134
115
|
self["total_bytes"]
|
|
135
116
|
end
|
|
136
117
|
|
|
137
|
-
# @return [Integer, nil]
|
|
138
118
|
def bytes_restored
|
|
139
119
|
self["bytes_restored"]
|
|
140
120
|
end
|
|
141
121
|
end
|
|
142
122
|
|
|
143
|
-
# One
|
|
144
|
-
# emitted when
|
|
123
|
+
# One +"verbose_status"+ message from a restore run, one per file. Only
|
|
124
|
+
# emitted when +verbose: true+ is passed to Yobi::Repository#restore.
|
|
145
125
|
class RestoreVerboseStatus < Yobi::FancyHash
|
|
146
|
-
# @return [String]
|
|
147
126
|
def action
|
|
148
127
|
self["action"]
|
|
149
128
|
end
|
|
150
129
|
|
|
151
|
-
# @return [String]
|
|
152
130
|
def item
|
|
153
131
|
self["item"]
|
|
154
132
|
end
|
|
155
133
|
|
|
156
|
-
# @return [Integer]
|
|
157
134
|
def size
|
|
158
135
|
self["size"]
|
|
159
136
|
end
|
|
@@ -2,29 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
5
|
+
# +restic rewrite+: creates new snapshots from existing ones with
|
|
6
6
|
# exclude/include filters applied, or metadata changed. With
|
|
7
|
-
#
|
|
7
|
+
# +snapshot_ids:+ and the other filters all left at their defaults,
|
|
8
8
|
# rewrites every snapshot in the repository.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# @param forget [Boolean] remove the original snapshots afterward, instead of tagging the new ones `"rewrite"` and keeping both
|
|
24
|
-
# @param new_host [String, nil] change the recorded hostname
|
|
25
|
-
# @param new_time [String, nil] change the recorded timestamp
|
|
26
|
-
# @param snapshot_summary [Boolean] regenerate the snapshot summary
|
|
27
|
-
# @return [true]
|
|
10
|
+
# +snapshot_ids:+ names the snapshots to rewrite, or all of them if
|
|
11
|
+
# empty. +hosts:+, +tags:+, and +paths:+ each accept a single value or
|
|
12
|
+
# an Array to filter by.
|
|
13
|
+
#
|
|
14
|
+
# +excludes:+, +exclude_files:+, +includes:+, +include_files:+ and
|
|
15
|
+
# their +i+-prefixed (case-insensitive) variants each accept a single
|
|
16
|
+
# value or an Array.
|
|
17
|
+
#
|
|
18
|
+
# +dry_run:+ reports what would happen without doing it. +forget:+
|
|
19
|
+
# removes the original snapshots afterward, instead of tagging the new
|
|
20
|
+
# ones +"rewrite"+ and keeping both. +new_host:+ / +new_time:+ change
|
|
21
|
+
# the recorded hostname/timestamp. +snapshot_summary:+ regenerates the
|
|
22
|
+
# snapshot summary.
|
|
28
23
|
def rewrite(snapshot_ids: [], hosts: [], tags: [], paths: [], excludes: [], exclude_files: [],
|
|
29
24
|
iexcludes: [], iexclude_files: [], includes: [], include_files: [], iincludes: [], iinclude_files: [],
|
|
30
25
|
dry_run: false, forget: false, new_host: nil, new_time: nil, snapshot_summary: false)
|
|
@@ -4,15 +4,14 @@ require "json"
|
|
|
4
4
|
|
|
5
5
|
module Yobi
|
|
6
6
|
class Repository
|
|
7
|
-
#
|
|
7
|
+
# +restic snapshots+: lists snapshots, optionally filtered.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# @return [Array<Yobi::Snapshot>]
|
|
9
|
+
# +tags:+, +hosts:+, and +paths:+ each accept a single value or an
|
|
10
|
+
# Array to filter by. +compact:+ compacts the printed listing.
|
|
11
|
+
# +group_by:+ (e.g. +"host"+) groups results. +latest:+ limits to the
|
|
12
|
+
# N most recent per group.
|
|
13
|
+
#
|
|
14
|
+
# Returns an Array of Yobi::Snapshot.
|
|
16
15
|
def snapshots(tags: [], hosts: [], paths: [], compact: false, group_by: nil, latest: nil)
|
|
17
16
|
argv = build_argv("snapshots") do |a|
|
|
18
17
|
a.repeat_flag(:tag, tags)
|
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
STATS_MODES = %w[restore-size files-by-contents blobs-per-file raw-data].each_with_object({}) do |value, hash|
|
|
5
|
+
STATS_MODES = %w[restore-size files-by-contents blobs-per-file raw-data].each_with_object({}) do |value, hash| # :nodoc:
|
|
7
6
|
hash[value] = value
|
|
8
7
|
hash[value.tr("-", "_").to_sym] = value
|
|
9
8
|
end.freeze
|
|
10
9
|
|
|
11
|
-
#
|
|
10
|
+
# +restic stats+: accumulates statistics about the repository's data.
|
|
12
11
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
12
|
+
# +snapshot_ids:+ restricts to specific snapshot IDs, or the whole
|
|
13
|
+
# repository if empty. +hosts:+, +paths:+, and +tags:+ each accept a
|
|
14
|
+
# single value or an Array to filter by.
|
|
15
|
+
#
|
|
16
|
+
# +mode:+ is +"restore-size"+ / +:restore_size+ (default),
|
|
17
|
+
# +"files-by-contents"+ / +:files_by_contents+, +"blobs-per-file"+ /
|
|
18
|
+
# +:blobs_per_file+, or +"raw-data"+ / +:raw_data+. Anything else
|
|
19
|
+
# raises ArgumentError.
|
|
20
|
+
#
|
|
21
|
+
# Returns a Yobi::RepositoryStats.
|
|
20
22
|
def stats(snapshot_ids: [], hosts: [], mode: nil, paths: [], tags: [])
|
|
21
23
|
argv = build_argv("stats", snapshot_ids) do |a|
|
|
22
24
|
a.repeat_flag(:host, hosts)
|
|
@@ -27,49 +29,52 @@ module Yobi
|
|
|
27
29
|
a.repeat_flag(:tag, tags)
|
|
28
30
|
end
|
|
29
31
|
execution = run_restic(argv)
|
|
30
|
-
|
|
32
|
+
# Permissive because Restic 0.19.0 prints a "[0:00] 100.00% ..."
|
|
33
|
+
# progress line before the JSON summary. Fixed in 0.19.1.
|
|
34
|
+
RepositoryStats.new(parse_json_output_permissively(execution))
|
|
31
35
|
end
|
|
32
36
|
end
|
|
33
37
|
|
|
34
|
-
# The result of one
|
|
35
|
-
# https://restic.readthedocs.io/en/stable/075_scripting.html#stats
|
|
38
|
+
# The result of one Yobi::Repository#stats call. See
|
|
39
|
+
# https://restic.readthedocs.io/en/stable/075_scripting.html#stats.
|
|
36
40
|
class RepositoryStats < Yobi::FancyHash
|
|
37
|
-
#
|
|
41
|
+
# Repository size in bytes.
|
|
38
42
|
def total_size
|
|
39
43
|
self["total_size"] || 0
|
|
40
44
|
end
|
|
41
45
|
|
|
42
|
-
#
|
|
46
|
+
# Number of files backed up in the repository.
|
|
43
47
|
def total_file_count
|
|
44
48
|
self["total_file_count"] || 0
|
|
45
49
|
end
|
|
46
50
|
|
|
47
|
-
#
|
|
51
|
+
# Number of blobs in the repository.
|
|
48
52
|
def total_blob_count
|
|
49
53
|
self["total_blob_count"] || 0
|
|
50
54
|
end
|
|
51
55
|
|
|
52
|
-
#
|
|
56
|
+
# Number of processed snapshots.
|
|
53
57
|
def snapshots_count
|
|
54
58
|
self["snapshots_count"] || 0
|
|
55
59
|
end
|
|
56
60
|
|
|
57
|
-
#
|
|
61
|
+
# Repository size in bytes if blobs were uncompressed.
|
|
58
62
|
def total_uncompressed_size
|
|
59
63
|
self["total_uncompressed_size"] || 0
|
|
60
64
|
end
|
|
61
65
|
|
|
62
|
-
#
|
|
66
|
+
# Factor by which the already compressed data has shrunk due to
|
|
67
|
+
# compression.
|
|
63
68
|
def compression_ratio
|
|
64
69
|
self["compression_ratio"] || 0.0
|
|
65
70
|
end
|
|
66
71
|
|
|
67
|
-
#
|
|
72
|
+
# Percentage of already compressed data.
|
|
68
73
|
def compression_progress
|
|
69
74
|
self["compression_progress"] || 0.0
|
|
70
75
|
end
|
|
71
76
|
|
|
72
|
-
#
|
|
77
|
+
# Overall space saving due to compression.
|
|
73
78
|
def compression_space_saving
|
|
74
79
|
self["compression_space_saving"] || 0.0
|
|
75
80
|
end
|
data/lib/yobi/repository/tag.rb
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# +restic tag+: modifies tags on existing snapshots. Tags are part of a
|
|
6
|
+
# snapshot's content-addressed identity, so changing them produces a
|
|
7
7
|
# new snapshot ID for each affected snapshot.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
9
|
+
# +snapshot_ids:+ names the snapshots to modify, or all matching
|
|
10
|
+
# +hosts:+/+paths:+ if empty.
|
|
11
|
+
#
|
|
12
|
+
# +add:+ appends tags, keeping existing ones. +remove:+ strips tags.
|
|
13
|
+
# +set:+ replaces all tags with exactly the given set (exclusive with
|
|
14
|
+
# +add:+/+remove:+).
|
|
15
|
+
#
|
|
16
|
+
# +tags:+, +hosts:+, and +paths:+ each accept a single value or an
|
|
17
|
+
# Array to filter which snapshots to modify (by current tags,
|
|
18
|
+
# hostname, or originally backed-up path respectively).
|
|
19
|
+
#
|
|
20
|
+
# Returns a Yobi::TagOutcome.
|
|
17
21
|
def tag(snapshot_ids: [], add: [], remove: [], set: [], tags: [], hosts: [], paths: [])
|
|
18
22
|
argv = build_argv("tag", snapshot_ids) do |a|
|
|
19
23
|
a.repeat_flag(:add, add)
|
|
@@ -29,16 +33,7 @@ module Yobi
|
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
# in the matching typed class. Used both as the {Yobi::ResticOutput}
|
|
34
|
-
# `transform:` for {Yobi::Repository#tag} and by {Yobi::TagOutcome}'s own
|
|
35
|
-
# post-hoc accessors.
|
|
36
|
-
#
|
|
37
|
-
# @private
|
|
38
|
-
module TagMessageWrapper
|
|
39
|
-
# @param raw [Hash]
|
|
40
|
-
# @return [Yobi::TagChange, Yobi::TagSummary, Hash]
|
|
41
|
-
# the raw Hash itself for a message_type this version of Yobi doesn't recognize
|
|
36
|
+
module TagMessageWrapper # :nodoc:
|
|
42
37
|
def self.call(raw)
|
|
43
38
|
case raw["message_type"]
|
|
44
39
|
when "changed" then TagChange.new(raw)
|
|
@@ -48,28 +43,26 @@ module Yobi
|
|
|
48
43
|
end
|
|
49
44
|
end
|
|
50
45
|
|
|
51
|
-
# The outcome of one
|
|
46
|
+
# The outcome of one Yobi::Repository#tag call.
|
|
52
47
|
class TagOutcome
|
|
53
|
-
#
|
|
48
|
+
# The Yobi::ResticOutput backing this outcome.
|
|
54
49
|
attr_reader :output
|
|
55
50
|
|
|
56
|
-
#
|
|
57
|
-
def initialize(execution)
|
|
51
|
+
def initialize(execution) # :nodoc:
|
|
58
52
|
@output = execution[:output]
|
|
59
53
|
end
|
|
60
54
|
|
|
61
|
-
#
|
|
55
|
+
# The Yobi::TagSummary of Restic's own +"summary"+ fields.
|
|
62
56
|
def summary
|
|
63
57
|
@summary ||= output.messages("summary").first || TagSummary.new({})
|
|
64
58
|
end
|
|
65
59
|
alias_method :report, :summary
|
|
66
60
|
|
|
67
|
-
#
|
|
61
|
+
# One Yobi::TagChange per snapshot actually modified.
|
|
68
62
|
def changes
|
|
69
63
|
@changes ||= output.messages("changed")
|
|
70
64
|
end
|
|
71
65
|
|
|
72
|
-
# @return [void]
|
|
73
66
|
def pretty_print(q)
|
|
74
67
|
q.object_group(self) do
|
|
75
68
|
q.breakable
|
|
@@ -78,25 +71,22 @@ module Yobi
|
|
|
78
71
|
end
|
|
79
72
|
end
|
|
80
73
|
|
|
81
|
-
# One
|
|
74
|
+
# One +"changed"+ message from a tag run. #old_snapshot_id is now stale:
|
|
82
75
|
# tags are part of a snapshot's content-addressed identity, so changing
|
|
83
76
|
# them produces a new snapshot ID.
|
|
84
77
|
class TagChange < Yobi::FancyHash
|
|
85
|
-
# @return [String]
|
|
86
78
|
def old_snapshot_id
|
|
87
79
|
self["old_snapshot_id"]
|
|
88
80
|
end
|
|
89
81
|
|
|
90
|
-
# @return [String]
|
|
91
82
|
def new_snapshot_id
|
|
92
83
|
self["new_snapshot_id"]
|
|
93
84
|
end
|
|
94
85
|
end
|
|
95
86
|
|
|
96
|
-
# The
|
|
97
|
-
# command finishes. Dispatched to
|
|
87
|
+
# The +"summary"+ message from a tag run, the final result once the
|
|
88
|
+
# command finishes. Dispatched to Yobi::TagOutcome#summary.
|
|
98
89
|
class TagSummary < Yobi::FancyHash
|
|
99
|
-
# @return [Integer]
|
|
100
90
|
def changed_snapshots
|
|
101
91
|
self["changed_snapshots"] || 0
|
|
102
92
|
end
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# @param remove_all [Boolean] remove every lock, not just stale ones
|
|
8
|
-
# @return [true]
|
|
5
|
+
# +restic unlock+: removes stale locks left by other Restic processes.
|
|
6
|
+
# +remove_all: true+ removes every lock, not just stale ones.
|
|
9
7
|
def unlock(remove_all: false)
|
|
10
8
|
argv = build_argv("unlock") do |a|
|
|
11
9
|
a.flag(:remove_all) if remove_all
|