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
data/lib/yobi/repository/cat.rb
CHANGED
|
@@ -2,52 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# @return [Hash]
|
|
8
|
-
# @raise [Yobi::RepositoryNotFound, Yobi::AuthenticationFailed]
|
|
5
|
+
# +restic cat config+: the repository's own config document, as a Hash.
|
|
6
|
+
# Raises Yobi::RepositoryNotFound or Yobi::AuthenticationFailed.
|
|
9
7
|
def cat_config
|
|
10
8
|
execution = run_restic(build_argv("cat", "config"))
|
|
11
9
|
parse_json_output(execution)
|
|
12
10
|
end
|
|
13
11
|
alias_method :config, :cat_config
|
|
14
12
|
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# @param id [String, Yobi::Snapshot]
|
|
18
|
-
# @return [Hash]
|
|
13
|
+
# +restic cat snapshot ID+: one snapshot's own raw stored record, as a
|
|
14
|
+
# Hash. +id+ can be a String or a Yobi::Snapshot.
|
|
19
15
|
def cat_snapshot(id)
|
|
20
16
|
id = id.id if id.is_a?(Yobi::Snapshot)
|
|
21
17
|
execution = run_restic(build_argv("cat", "snapshot", id))
|
|
22
18
|
parse_json_output(execution)
|
|
23
19
|
end
|
|
24
20
|
|
|
25
|
-
#
|
|
26
|
-
# from
|
|
27
|
-
#
|
|
28
|
-
# @param id [String]
|
|
29
|
-
# @return [Hash]
|
|
21
|
+
# +restic cat index ID+: one index file's own raw contents, as a Hash.
|
|
22
|
+
# IDs come from +list(:index)+.
|
|
30
23
|
def cat_index(id)
|
|
31
24
|
execution = run_restic(build_argv("cat", "index", id))
|
|
32
25
|
parse_json_output(execution)
|
|
33
26
|
end
|
|
34
27
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# @param id [String, Yobi::Key]
|
|
38
|
-
# @return [Hash]
|
|
28
|
+
# +restic cat key ID+: one key's own raw stored record, as a Hash. +id+
|
|
29
|
+
# can be a String or a Yobi::Key.
|
|
39
30
|
def cat_key(id)
|
|
40
31
|
id = id.id if id.is_a?(Yobi::Key)
|
|
41
32
|
execution = run_restic(build_argv("cat", "key", id))
|
|
42
33
|
parse_json_output(execution)
|
|
43
34
|
end
|
|
44
35
|
|
|
45
|
-
#
|
|
46
|
-
# snapshot's root, or at
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# @param subfolder [String, nil]
|
|
50
|
-
# @return [Hash]
|
|
36
|
+
# +restic cat tree snapshot:subfolder+: the raw tree object at a
|
|
37
|
+
# snapshot's root, or at +subfolder+ within it. +snapshot_id+ can be a
|
|
38
|
+
# String, a Yobi::Snapshot, or Restic's own +"snapshotID:subfolder"+
|
|
39
|
+
# form directly.
|
|
51
40
|
def cat_tree(snapshot_id, subfolder: nil)
|
|
52
41
|
snapshot_id = snapshot_id.id if snapshot_id.is_a?(Yobi::Snapshot)
|
|
53
42
|
target = if subfolder.nil?
|
|
@@ -59,34 +48,25 @@ module Yobi
|
|
|
59
48
|
parse_json_output(execution)
|
|
60
49
|
end
|
|
61
50
|
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# @param id [String]
|
|
66
|
-
# @yieldparam io [IO]
|
|
67
|
-
# @return [Yobi::IOHandle] if no block is given
|
|
51
|
+
# +restic cat pack ID+: one pack file's raw, still-encrypted bytes. IDs
|
|
52
|
+
# come from +list(:packs)+. Same block/handle shape as #dump: without a
|
|
53
|
+
# block, returns a Yobi::IOHandle.
|
|
68
54
|
def cat_pack(id, &block)
|
|
69
55
|
run_restic_dump(build_argv("cat", "pack", id), &block)
|
|
70
56
|
end
|
|
71
57
|
|
|
72
|
-
#
|
|
73
|
-
# from
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# @param id [String]
|
|
77
|
-
# @yieldparam io [IO]
|
|
78
|
-
# @return [Yobi::IOHandle] if no block is given
|
|
58
|
+
# +restic cat blob ID+: one data blob's raw, decrypted bytes. IDs come
|
|
59
|
+
# from #cat_tree's own node +"content"+ arrays. Same block/handle shape
|
|
60
|
+
# as #dump.
|
|
79
61
|
def cat_blob(id, &block)
|
|
80
62
|
run_restic_dump(build_argv("cat", "blob", id), &block)
|
|
81
63
|
end
|
|
82
64
|
|
|
83
|
-
#
|
|
84
|
-
# material. Extremely sensitive: this is the actual key, not a
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
# @return [Hash]
|
|
65
|
+
# +restic cat masterkey+: this repository's own encryption/MAC key
|
|
66
|
+
# material. Extremely sensitive: this is the actual key, not a redacted
|
|
67
|
+
# reference to it, and there is no operation that rotates it; every
|
|
68
|
+
# other key/password management method here only manages different ways
|
|
69
|
+
# to unlock this same master key.
|
|
90
70
|
def cat_masterkey_and_game_over_if_this_leaks
|
|
91
71
|
execution = run_restic(build_argv("cat", "masterkey"))
|
|
92
72
|
parse_json_output(execution)
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
5
|
+
# +restic check+: tests the repository for errors.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
7
|
+
# +hosts:+, +paths:+ and +tags:+ each accept a single value or an Array
|
|
8
|
+
# to filter by.
|
|
9
|
+
#
|
|
10
|
+
# +read_data:+ reads and verifies pack file contents, not just structure.
|
|
11
|
+
# +read_data_subset:+ narrows that to a subset of packs, e.g. +"5%"+.
|
|
12
|
+
# +with_cache:+ uses the local cache.
|
|
13
|
+
#
|
|
14
|
+
# Returns a Yobi::CheckOutcome.
|
|
14
15
|
def check(hosts: [], paths: [], read_data: false, read_data_subset: nil, tags: [], with_cache: false)
|
|
15
16
|
argv = build_argv("check") do |a|
|
|
16
17
|
a.repeat_flag(:host, hosts)
|
|
@@ -26,16 +27,7 @@ module Yobi
|
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
# it in the matching typed class. Used both as the {Yobi::ResticOutput}
|
|
31
|
-
# `transform:` for {Yobi::Repository#check} and by {Yobi::CheckOutcome}'s
|
|
32
|
-
# own post-hoc accessors.
|
|
33
|
-
#
|
|
34
|
-
# @private
|
|
35
|
-
module CheckMessageWrapper
|
|
36
|
-
# @param raw [Hash]
|
|
37
|
-
# @return [Yobi::CheckError, Yobi::CheckSummary, Hash]
|
|
38
|
-
# the raw Hash itself for a message_type this version of Yobi doesn't recognize
|
|
30
|
+
module CheckMessageWrapper # :nodoc:
|
|
39
31
|
def self.call(raw)
|
|
40
32
|
case raw["message_type"]
|
|
41
33
|
when "error" then CheckError.new(raw)
|
|
@@ -45,28 +37,26 @@ module Yobi
|
|
|
45
37
|
end
|
|
46
38
|
end
|
|
47
39
|
|
|
48
|
-
# The outcome of one
|
|
40
|
+
# The outcome of one Yobi::Repository#check call.
|
|
49
41
|
class CheckOutcome
|
|
50
|
-
#
|
|
42
|
+
# The Yobi::ResticOutput backing this outcome.
|
|
51
43
|
attr_reader :output
|
|
52
44
|
|
|
53
|
-
#
|
|
54
|
-
def initialize(execution)
|
|
45
|
+
def initialize(execution) # :nodoc:
|
|
55
46
|
@output = execution[:output]
|
|
56
47
|
end
|
|
57
48
|
|
|
58
|
-
#
|
|
49
|
+
# The Yobi::CheckSummary of Restic's own +"summary"+ fields.
|
|
59
50
|
def summary
|
|
60
51
|
@summary ||= output.messages("summary").first || CheckSummary.new({})
|
|
61
52
|
end
|
|
62
53
|
alias_method :report, :summary
|
|
63
54
|
|
|
64
|
-
#
|
|
55
|
+
# Every Yobi::CheckError from the run.
|
|
65
56
|
def errors
|
|
66
57
|
@errors ||= output.messages("error")
|
|
67
58
|
end
|
|
68
59
|
|
|
69
|
-
# @return [void]
|
|
70
60
|
def pretty_print(q)
|
|
71
61
|
q.object_group(self) do
|
|
72
62
|
q.breakable
|
|
@@ -77,34 +67,31 @@ module Yobi
|
|
|
77
67
|
end
|
|
78
68
|
end
|
|
79
69
|
|
|
80
|
-
# One
|
|
81
|
-
# https://restic.readthedocs.io/en/stable/075_scripting.html#error
|
|
70
|
+
# One +"error"+ message from a check run.
|
|
82
71
|
class CheckError < Yobi::FancyHash
|
|
83
|
-
# @return [String]
|
|
84
72
|
def message
|
|
85
73
|
self["message"]
|
|
86
74
|
end
|
|
87
75
|
end
|
|
88
76
|
|
|
89
|
-
# The
|
|
90
|
-
# command finishes. Dispatched to
|
|
77
|
+
# The +"summary"+ message from a check run, the final result once the
|
|
78
|
+
# command finishes. Dispatched to Yobi::CheckOutcome#summary.
|
|
91
79
|
class CheckSummary < Yobi::FancyHash
|
|
92
|
-
# @return [Integer]
|
|
93
80
|
def num_errors
|
|
94
81
|
self["num_errors"] || 0
|
|
95
82
|
end
|
|
96
83
|
|
|
97
|
-
#
|
|
84
|
+
# Pack IDs needing #repair_packs / #repair_snapshots.
|
|
98
85
|
def broken_packs
|
|
99
86
|
self["broken_packs"] || []
|
|
100
87
|
end
|
|
101
88
|
|
|
102
|
-
#
|
|
89
|
+
# Whether to run Yobi::Repository#repair_index.
|
|
103
90
|
def suggest_repair_index?
|
|
104
91
|
self["suggest_repair_index"]
|
|
105
92
|
end
|
|
106
93
|
|
|
107
|
-
#
|
|
94
|
+
# Whether to run Yobi::Repository#prune.
|
|
108
95
|
def suggest_prune?
|
|
109
96
|
self["suggest_prune"]
|
|
110
97
|
end
|
data/lib/yobi/repository/copy.rb
CHANGED
|
@@ -2,20 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# +restic copy+: replicates snapshots from another repository into this
|
|
6
|
+
# one. Already-copied snapshots are skipped automatically.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# @param tags [Array<String>, String] filter by tag(s)
|
|
18
|
-
# @return [true]
|
|
8
|
+
# +from_repo:+ is the source repository's URL, a Yobi::Repository (its
|
|
9
|
+
# own #url/#password are used automatically), or a +[:file, "..."]+
|
|
10
|
+
# tuple reading the URL from a file. +from_password:+ takes the same
|
|
11
|
+
# shape as #initialize's +password:+, and defaults to the source
|
|
12
|
+
# Repository's own password when +from_repo:+ is one.
|
|
13
|
+
#
|
|
14
|
+
# +snapshot_ids:+ names the snapshots to copy; all of them if empty.
|
|
15
|
+
# +hosts:+, +paths:+, and +tags:+ each accept a single value or an
|
|
16
|
+
# Array to filter by.
|
|
19
17
|
def copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, hosts: [], paths: [], tags: [])
|
|
20
18
|
from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo)
|
|
21
19
|
from_password = from_repo_password(from_repo) if from_password.nil?
|
data/lib/yobi/repository/diff.rb
CHANGED
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# @param to [String] snapshot ID
|
|
9
|
-
# @param metadata [Boolean] also report metadata-only changes (access mode, timestamps, ...)
|
|
10
|
-
# @return [Yobi::DiffOutcome]
|
|
5
|
+
# +restic diff+: compares two snapshots, +from:+ and +to:+ (both
|
|
6
|
+
# snapshot IDs). +metadata: true+ also reports metadata-only changes
|
|
7
|
+
# (access mode, timestamps, ...). Returns a Yobi::DiffOutcome.
|
|
11
8
|
def diff(from:, to:, metadata: false)
|
|
12
9
|
argv = build_argv("diff", from, to) do |a|
|
|
13
10
|
a.flag(:metadata) if metadata
|
|
@@ -18,16 +15,7 @@ module Yobi
|
|
|
18
15
|
end
|
|
19
16
|
end
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
# it in the matching typed class. Used both as the {Yobi::ResticOutput}
|
|
23
|
-
# `transform:` for {Yobi::Repository#diff} and by {Yobi::DiffOutcome}'s
|
|
24
|
-
# own post-hoc accessors.
|
|
25
|
-
#
|
|
26
|
-
# @private
|
|
27
|
-
module DiffMessageWrapper
|
|
28
|
-
# @param raw [Hash]
|
|
29
|
-
# @return [Yobi::DiffChange, Yobi::DiffStatistics, Hash]
|
|
30
|
-
# the raw Hash itself for a message_type this version of Yobi doesn't recognize
|
|
18
|
+
module DiffMessageWrapper # :nodoc:
|
|
31
19
|
def self.call(raw)
|
|
32
20
|
case raw["message_type"]
|
|
33
21
|
when "change" then DiffChange.new(raw)
|
|
@@ -37,28 +25,26 @@ module Yobi
|
|
|
37
25
|
end
|
|
38
26
|
end
|
|
39
27
|
|
|
40
|
-
# The outcome of one
|
|
28
|
+
# The outcome of one Yobi::Repository#diff call.
|
|
41
29
|
class DiffOutcome
|
|
42
|
-
#
|
|
30
|
+
# The Yobi::ResticOutput backing this outcome.
|
|
43
31
|
attr_reader :output
|
|
44
32
|
|
|
45
|
-
#
|
|
46
|
-
def initialize(execution)
|
|
33
|
+
def initialize(execution) # :nodoc:
|
|
47
34
|
@output = execution[:output]
|
|
48
35
|
end
|
|
49
36
|
|
|
50
|
-
#
|
|
37
|
+
# The Yobi::DiffStatistics of Restic's own +"statistics"+ fields.
|
|
51
38
|
def statistics
|
|
52
39
|
@statistics ||= output.messages("statistics").first || DiffStatistics.new({})
|
|
53
40
|
end
|
|
54
41
|
alias_method :report, :statistics
|
|
55
42
|
|
|
56
|
-
#
|
|
43
|
+
# Every Yobi::DiffChange from the run.
|
|
57
44
|
def changes
|
|
58
45
|
@changes ||= output.messages("change")
|
|
59
46
|
end
|
|
60
47
|
|
|
61
|
-
# @return [void]
|
|
62
48
|
def pretty_print(q)
|
|
63
49
|
q.object_group(self) do
|
|
64
50
|
q.breakable
|
|
@@ -67,109 +53,92 @@ module Yobi
|
|
|
67
53
|
end
|
|
68
54
|
end
|
|
69
55
|
|
|
70
|
-
# One
|
|
71
|
-
# concatenation of single-character codes:
|
|
72
|
-
#
|
|
73
|
-
#
|
|
56
|
+
# One +"change"+ message from a diff run. #modifier is Restic's own
|
|
57
|
+
# concatenation of single-character codes: +"+"+ added, +"-"+ removed,
|
|
58
|
+
# +"U"+ metadata updated, +"M"+ content modified, +"T"+ type changed,
|
|
59
|
+
# +"?"+ bitrot detected.
|
|
74
60
|
class DiffChange < Yobi::FancyHash
|
|
75
|
-
# @return [String]
|
|
76
61
|
def path
|
|
77
62
|
self["path"]
|
|
78
63
|
end
|
|
79
64
|
|
|
80
|
-
# @return [String]
|
|
81
65
|
def modifier
|
|
82
66
|
self["modifier"]
|
|
83
67
|
end
|
|
84
68
|
|
|
85
|
-
# @return [Boolean]
|
|
86
69
|
def added?
|
|
87
70
|
modifier.include?("+")
|
|
88
71
|
end
|
|
89
72
|
|
|
90
|
-
# @return [Boolean]
|
|
91
73
|
def removed?
|
|
92
74
|
modifier.include?("-")
|
|
93
75
|
end
|
|
94
76
|
|
|
95
|
-
# @return [Boolean]
|
|
96
77
|
def type_changed?
|
|
97
78
|
modifier.include?("T")
|
|
98
79
|
end
|
|
99
80
|
|
|
100
|
-
# @return [Boolean]
|
|
101
81
|
def modified?
|
|
102
82
|
modifier.include?("M")
|
|
103
83
|
end
|
|
104
84
|
|
|
105
|
-
# @return [Boolean]
|
|
106
85
|
def metadata_updated?
|
|
107
86
|
modifier.include?("U")
|
|
108
87
|
end
|
|
109
88
|
|
|
110
|
-
# @return [Boolean]
|
|
111
89
|
def bitrot?
|
|
112
90
|
modifier.include?("?")
|
|
113
91
|
end
|
|
114
92
|
end
|
|
115
93
|
|
|
116
|
-
# The
|
|
117
|
-
# command finishes. Dispatched to
|
|
94
|
+
# The +"statistics"+ message from a diff run, the final result once the
|
|
95
|
+
# command finishes. Dispatched to Yobi::DiffOutcome#statistics.
|
|
118
96
|
class DiffStatistics < Yobi::FancyHash
|
|
119
|
-
# @return [String, nil]
|
|
120
97
|
def source_snapshot
|
|
121
98
|
self["source_snapshot"]
|
|
122
99
|
end
|
|
123
100
|
|
|
124
|
-
# @return [String, nil]
|
|
125
101
|
def target_snapshot
|
|
126
102
|
self["target_snapshot"]
|
|
127
103
|
end
|
|
128
104
|
|
|
129
|
-
# @return [Integer]
|
|
130
105
|
def changed_files
|
|
131
106
|
self["changed_files"] || 0
|
|
132
107
|
end
|
|
133
108
|
|
|
134
|
-
#
|
|
109
|
+
# A Yobi::DiffStat of what was added.
|
|
135
110
|
def added
|
|
136
111
|
@added ||= DiffStat.new(self["added"] || {})
|
|
137
112
|
end
|
|
138
113
|
|
|
139
|
-
#
|
|
114
|
+
# A Yobi::DiffStat of what was removed.
|
|
140
115
|
def removed
|
|
141
116
|
@removed ||= DiffStat.new(self["removed"] || {})
|
|
142
117
|
end
|
|
143
118
|
end
|
|
144
119
|
|
|
145
|
-
# The
|
|
120
|
+
# The +"added"+ / +"removed"+ sub-object of a Yobi::DiffStatistics message.
|
|
146
121
|
class DiffStat < Yobi::FancyHash
|
|
147
|
-
# @return [Integer]
|
|
148
122
|
def files
|
|
149
123
|
self["files"] || 0
|
|
150
124
|
end
|
|
151
125
|
|
|
152
|
-
# @return [Integer]
|
|
153
126
|
def dirs
|
|
154
127
|
self["dirs"] || 0
|
|
155
128
|
end
|
|
156
129
|
|
|
157
|
-
# @return [Integer]
|
|
158
130
|
def others
|
|
159
131
|
self["others"] || 0
|
|
160
132
|
end
|
|
161
133
|
|
|
162
|
-
# @return [Integer]
|
|
163
134
|
def data_blobs
|
|
164
135
|
self["data_blobs"] || 0
|
|
165
136
|
end
|
|
166
137
|
|
|
167
|
-
# @return [Integer]
|
|
168
138
|
def tree_blobs
|
|
169
139
|
self["tree_blobs"] || 0
|
|
170
140
|
end
|
|
171
141
|
|
|
172
|
-
# @return [Integer]
|
|
173
142
|
def bytes
|
|
174
143
|
self["bytes"] || 0
|
|
175
144
|
end
|
data/lib/yobi/repository/dump.rb
CHANGED
|
@@ -4,24 +4,23 @@ require "tempfile"
|
|
|
4
4
|
|
|
5
5
|
module Yobi
|
|
6
6
|
class Repository
|
|
7
|
-
#
|
|
8
|
-
ARCHIVE_TYPES = Set.new(%w[tar zip].flat_map { [_1, _1.to_sym] }).freeze
|
|
7
|
+
ARCHIVE_TYPES = Set.new(%w[tar zip].flat_map { [_1, _1.to_sym] }).freeze # :nodoc:
|
|
9
8
|
|
|
10
|
-
#
|
|
9
|
+
# +restic dump+: extracts a file/folder from a snapshot. A single file's
|
|
11
10
|
# raw bytes are written as-is; a folder is written as a tar/zip archive.
|
|
12
|
-
# Give at most one of
|
|
11
|
+
# Give at most one of +target:+ or a block.
|
|
13
12
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
13
|
+
# +snapshot_id:+ is a snapshot ID, or Restic's own
|
|
14
|
+
# +"snapshotID:subfolder"+ form. +file:+ is the path within the snapshot
|
|
15
|
+
# to extract; +"/"+ dumps the whole snapshot. +archive:+ is +"tar"+
|
|
16
|
+
# (default) or +"zip"+, and only applies when +file+ is a folder.
|
|
17
|
+
#
|
|
18
|
+
# +hosts:+, +paths:+ and +tags:+ are only relevant when +snapshot_id+
|
|
19
|
+
# is +"latest"+.
|
|
20
|
+
#
|
|
21
|
+
# With +target:+, writes straight to that file path and returns +true+.
|
|
22
|
+
# Without a block, returns a Yobi::IOHandle. Raises ArgumentError if
|
|
23
|
+
# given both +target:+ and a block.
|
|
25
24
|
def dump(snapshot_id:, file:, target: nil, archive: nil, hosts: [], paths: [], tags: [], &block)
|
|
26
25
|
raise ArgumentError, "give at most one of target: or a block, not both" if target && block
|
|
27
26
|
|
data/lib/yobi/repository/find.rb
CHANGED
|
@@ -4,25 +4,20 @@ require "time"
|
|
|
4
4
|
|
|
5
5
|
module Yobi
|
|
6
6
|
class Repository
|
|
7
|
-
#
|
|
7
|
+
# +restic find+: searches for files/directories across snapshots by
|
|
8
8
|
# name pattern.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# @param reverse [Boolean] reverse sort order
|
|
22
|
-
# @param show_pack_id [Boolean] include the pack ID each match is stored in
|
|
23
|
-
# @param snapshot_ids [Array<String>, String] restrict to these snapshot ID(s)
|
|
24
|
-
# @param tags [Array<String>, String] filter by tag(s)
|
|
25
|
-
# @return [Array<Yobi::MatchesPerSnapshot>]
|
|
10
|
+
# +patterns:+ accepts a single glob or an Array. +blob:+, +pack:+ and
|
|
11
|
+
# +tree:+ match blob/pack/tree IDs instead of file names.
|
|
12
|
+
#
|
|
13
|
+
# +hosts:+, +paths:+, +snapshot_ids:+, and +tags:+ each accept a single
|
|
14
|
+
# value or an Array to filter by. +newest:+ / +oldest:+ restrict the
|
|
15
|
+
# snapshot window.
|
|
16
|
+
#
|
|
17
|
+
# +ignore_case:+, +long:+, +human_readable:+, +reverse:+ and
|
|
18
|
+
# +show_pack_id:+ toggle formatting/matching options.
|
|
19
|
+
#
|
|
20
|
+
# Returns an Array of Yobi::MatchesPerSnapshot.
|
|
26
21
|
def find(patterns:, blob: false, pack: false, tree: false, hosts: [], human_readable: false,
|
|
27
22
|
ignore_case: false, long: false, newest: nil, oldest: nil, paths: [], reverse: false,
|
|
28
23
|
show_pack_id: false, snapshot_ids: [], tags: [])
|
|
@@ -48,88 +43,73 @@ module Yobi
|
|
|
48
43
|
end
|
|
49
44
|
end
|
|
50
45
|
|
|
51
|
-
# One matched file/directory from a
|
|
46
|
+
# One matched file/directory from a Yobi::Repository#find call.
|
|
52
47
|
class FindMatch < Yobi::FancyHash
|
|
53
|
-
# @return [String]
|
|
54
48
|
def path
|
|
55
49
|
self["path"]
|
|
56
50
|
end
|
|
57
51
|
|
|
58
|
-
# @return [String]
|
|
59
52
|
def type
|
|
60
53
|
self["type"]
|
|
61
54
|
end
|
|
62
55
|
|
|
63
|
-
# @return [Integer]
|
|
64
56
|
def size
|
|
65
57
|
self["size"]
|
|
66
58
|
end
|
|
67
59
|
|
|
68
|
-
# @return [String]
|
|
69
60
|
def permissions
|
|
70
61
|
self["permissions"]
|
|
71
62
|
end
|
|
72
63
|
|
|
73
|
-
# @return [Integer]
|
|
74
64
|
def uid
|
|
75
65
|
self["uid"]
|
|
76
66
|
end
|
|
77
67
|
|
|
78
|
-
# @return [Integer]
|
|
79
68
|
def gid
|
|
80
69
|
self["gid"]
|
|
81
70
|
end
|
|
82
71
|
|
|
83
|
-
# @return [String]
|
|
84
72
|
def user
|
|
85
73
|
self["user"]
|
|
86
74
|
end
|
|
87
75
|
|
|
88
|
-
# @return [String]
|
|
89
76
|
def group
|
|
90
77
|
self["group"]
|
|
91
78
|
end
|
|
92
79
|
|
|
93
|
-
# @return [Integer]
|
|
94
80
|
def inode
|
|
95
81
|
self["inode"]
|
|
96
82
|
end
|
|
97
83
|
|
|
98
|
-
# @return [Time]
|
|
99
84
|
def mtime
|
|
100
85
|
@mtime ||= Time.parse(self["mtime"])
|
|
101
86
|
end
|
|
102
87
|
|
|
103
|
-
# @return [Time]
|
|
104
88
|
def atime
|
|
105
89
|
@atime ||= Time.parse(self["atime"])
|
|
106
90
|
end
|
|
107
91
|
|
|
108
|
-
# @return [Time]
|
|
109
92
|
def ctime
|
|
110
93
|
@ctime ||= Time.parse(self["ctime"])
|
|
111
94
|
end
|
|
112
95
|
end
|
|
113
96
|
|
|
114
|
-
# One snapshot's worth of matches from a
|
|
97
|
+
# One snapshot's worth of matches from a Yobi::Repository#find call.
|
|
115
98
|
class MatchesPerSnapshot < Yobi::FancyHash
|
|
116
|
-
# Restic's own
|
|
99
|
+
# Restic's own +find --json+ output only gives the snapshot's ID here,
|
|
117
100
|
# not a full record - unlike every other place in this API where
|
|
118
|
-
# "snapshot" means a
|
|
101
|
+
# "snapshot" means a Yobi::Snapshot. Call Yobi::Repository#snapshots
|
|
119
102
|
# yourself if you need the full record.
|
|
120
|
-
#
|
|
121
|
-
# @return [String]
|
|
122
103
|
def snapshot
|
|
123
104
|
self["snapshot"]
|
|
124
105
|
end
|
|
125
106
|
alias_method :snapshot_id, :snapshot
|
|
126
107
|
|
|
127
|
-
# @return [Integer]
|
|
128
108
|
def hits
|
|
129
109
|
self["hits"]
|
|
130
110
|
end
|
|
131
111
|
|
|
132
|
-
#
|
|
112
|
+
# An Array of Yobi::FindMatch objects for this snapshot.
|
|
133
113
|
def matches
|
|
134
114
|
@matches ||= (self["matches"] || []).map { |raw| Yobi::FindMatch.new(raw) }
|
|
135
115
|
end
|