fce44ae9 Andy McCall 1 month ago ↶
1
2
2863456a Andy McCall 1 month ago ↶
3
use super :: format:: { deps_line, deps_of, plural, short_id, short_meta_id, signature_line, when} ;
fce44ae9 Andy McCall 1 month ago ↶
4
use super :: open;
135212de Andy McCall 1 month ago ↶
5
use super :: ui;
fce44ae9 Andy McCall 1 month ago ↶
6
use anyhow:: Result;
7
use oxy_core:: repo:: { render_change, ChangeStatus, Repo} ;
8
use std:: path:: PathBuf;
9
10
pub fn init ( path : Option< PathBuf> ) -> Result< i32 > {
11
let path = path.unwrap_or ( std:: env:: current_dir( ) ? ) ;
12
let repo = Repo:: init( & path) ? ;
135212de Andy McCall 1 month ago ↶
13
println! ( " {} oxy repository in {} " , ui:: ok( " Initialized" ) , ui:: path( repo.root ( ) .display ( ) ) ) ;
14
println! ( " bookmark {} author {} <{} >" , ui:: id( & repo.config.bookmark) , repo.config.author_name, repo.config.author_email) ;
fce44ae9 Andy McCall 1 month ago ↶
15
Ok ( 0 )
16
}
17
18
fn status_tag ( s : ChangeStatus) -> & 'static str {
19
match s {
20
ChangeStatus:: Added => " A" ,
21
ChangeStatus:: Modified => " M" ,
22
ChangeStatus:: Removed => " D" ,
23
ChangeStatus:: ModeChanged => " X" ,
24
ChangeStatus:: ConflictEdited => " C!" ,
25
ChangeStatus:: Resolved => " R" ,
26
}
27
}
28
29
pub fn status ( ) -> Result< i32 > {
30
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
31
let sp = ui:: spinner( " Snapshotting the working copy…" ) ;
fce44ae9 Andy McCall 1 month ago ↶
32
let snap = repo.snapshot ( " oxy status" ) ? ;
33
let heads = repo.bookmark_heads ( ) ? ;
135212de Andy McCall 1 month ago ↶
34
sp.clear ( ) ;
35
println! (
36
" {} {} on {} {} " ,
37
ui:: heading( " Working copy" ) ,
38
ui:: id( snap.wc.change_id.short ( 8 ) ) ,
39
ui:: id( & repo.config.bookmark) ,
40
ui:: dim( format! ( " ({} )" , plural ( heads.len ( ) , " head" ) ) )
41
) ;
fce44ae9 Andy McCall 1 month ago ↶
42
if snap.statuses.is_empty ( ) {
135212de Andy McCall 1 month ago ↶
43
println! ( " {} " , ui:: dim( " clean" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
44
}
45
for s in & snap.statuses {
135212de Andy McCall 1 month ago ↶
46
println! ( " {:2} {} " , ui:: tag( status_tag ( s.status) ) , ui:: path( & s.path) ) ;
fce44ae9 Andy McCall 1 month ago ↶
47
}
48
if ! snap.base_snapshot.is_conflicted ( ) {
49
return Ok ( 0 ) ;
50
}
135212de Andy McCall 1 month ago ↶
51
println! ( ) ;
52
println! ( " {} {} " , ui:: err( " Conflicts" ) , ui:: dim( " edit the marked files, or the .oxy-conflict-N candidates, then commit" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
53
for c in & snap.base_snapshot.tree.conflicts {
54
println! ( " tree: {c:?} " ) ;
55
}
56
for ( p, f) in & snap.base_snapshot.files {
57
if f.is_conflicted ( ) {
58
let state = match snap.statuses.iter ( ) .find ( | s | s.path = = * p ) .map ( | s | s.status ) {
135212de Andy McCall 1 month ago ↶
59
Some ( ChangeStatus:: Resolved) => ui:: ok( " resolved in working copy" ) ,
60
Some ( ChangeStatus:: ConflictEdited) => ui:: warn( " markers remain" ) ,
61
_ => ui:: err( " unresolved" ) ,
fce44ae9 Andy McCall 1 month ago ↶
62
} ;
135212de Andy McCall 1 month ago ↶
63
println! ( " {} {state} " , ui:: path( p) ) ;
fce44ae9 Andy McCall 1 month ago ↶
64
}
65
}
66
Ok ( 2 )
67
}
68
69
pub fn commit ( message : & str , depends_on : & [String]) -> Result< i32 > {
70
let repo = open ( ) ? ;
71
let mut deps = Vec :: new( ) ;
72
for d in depends_on {
73
deps.push ( repo.resolve_change ( d) ? ) ;
74
}
135212de Andy McCall 1 month ago ↶
75
let sp = ui:: spinner( " Committing…" ) ;
fce44ae9 Andy McCall 1 month ago ↶
76
let h = repo.commit ( message, & deps) ? ;
77
let c = repo.store ( ) .get_change ( & h) ? ;
135212de Andy McCall 1 month ago ↶
78
sp.done ( & format! ( " {} {} {} {} " , ui:: ok( " Committed" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , h.short ( 12 ) ) ) , message.lines ( ) .next ( ) .unwrap_or ( " " ) ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
79
Ok ( 0 )
80
}
81
82
pub fn log ( ) -> Result< i32 > {
83
let repo = open ( ) ? ;
84
repo.snapshot ( " oxy log" ) ? ;
85
for e in repo.log ( ) ? {
86
let c = & e.change;
87
let mut flags = Vec :: new( ) ;
88
if e.is_working_copy {
89
flags.push ( " working copy" ) ;
90
}
91
if e.is_head {
92
flags.push ( " head" ) ;
93
}
135212de Andy McCall 1 month ago ↶
94
let flags = if flags.is_empty ( ) { String :: new( ) } else { ui:: warn( format! ( " ({} )" , flags.join ( " , " ) ) ) } ;
2863456a Andy McCall 1 month ago ↶
95
println! ( " {} {} {} " , ui:: id( short_meta_id ( c) ) , ui:: hash( format! ( " #{} " , e.hash.short ( 12 ) ) ) , flags) ;
135212de Andy McCall 1 month ago ↶
96
println! ( " {} " , ui:: dim( when ( c.hlc.wall_ms) ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
97
if ! c.deps.is_empty ( ) {
2863456a Andy McCall 1 month ago ↶
98
println! ( " {} " , ui:: dim( format! ( " deps: {} " , deps_of ( & c.deps) ) ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
99
}
100
let msg = if c.message.is_empty ( ) { " (no description)" } else { & c.message } ;
101
for line in msg.lines ( ) {
102
println! ( " {line} " ) ;
103
}
104
println! ( ) ;
105
}
106
Ok ( 0 )
107
}
108
109
pub fn diff ( ) -> Result< i32 > {
110
let repo = open ( ) ? ;
111
let snap = repo.snapshot ( " oxy diff" ) ? ;
112
print! ( " {} " , render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ) ;
113
Ok ( 0 )
114
}
115
116
pub fn show ( change : & str ) -> Result< i32 > {
117
let repo = open ( ) ? ;
118
repo.snapshot ( " oxy show" ) ? ;
119
let h = repo.resolve_change ( change) ? ;
120
let c = repo.store ( ) .get_change ( & h) ? ;
135212de Andy McCall 1 month ago ↶
121
println! ( " {} {} {} " , ui:: heading( " change" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{h} " ) ) ) ;
122
println! ( " kind {:?} " , c.kind) ;
123
println! ( " date {} " , when ( c.hlc.wall_ms) ) ;
124
println! ( " deps {} " , ui:: dim( deps_line ( & c) ) ) ;
125
println! ( " sig {} " , signature_line ( & repo, & h) ? ) ;
fce44ae9 Andy McCall 1 month ago ↶
126
println! ( " \n {} \n " , if c.message.is_empty ( ) { " (no description)" } else { & c.message } ) ;
127
print! ( " {} " , repo.describe_change ( & h) ? ) ;
128
Ok ( 0 )
129
}
130
131
pub fn checkout ( changes : & [String], discard : bool ) -> Result< i32 > {
132
let repo = open ( ) ? ;
133
let heads = if changes.is_empty ( ) {
134
repo.bookmark_heads ( ) ?
135
} else {
132b884b Andy McCall 1 month ago ↶
136
let mut heads = Vec :: new( ) ;
137
for c in changes {
138
heads.extend ( repo.resolve_heads ( c) ? ) ;
139
}
140
heads
fce44ae9 Andy McCall 1 month ago ↶
141
} ;
142
let spec = if changes.is_empty ( ) { repo.config.bookmark.clone ( ) } else { changes.join ( " " ) } ;
135212de Andy McCall 1 month ago ↶
143
let sp = ui:: spinner( & format! ( " Checking out {spec} …" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
144
let r = repo.checkout ( & heads, discard, & format! ( " oxy checkout {spec} " ) ) ? ;
135212de Andy McCall 1 month ago ↶
145
sp.done ( & format! ( " {} on {} : {} written, {} removed" , ui:: ok( " Working copy" ) , ui:: id( & spec) , plural ( r.written, " file" ) , r.removed) ) ;
fce44ae9 Andy McCall 1 month ago ↶
146
if r.conflicted {
135212de Andy McCall 1 month ago ↶
147
println! ( " {} " , ui:: warn( " this state has conflicts; see oxy status" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
148
}
149
Ok ( 0 )
150
}
151
152
pub fn resolve ( ) -> Result< i32 > {
153
let repo = open ( ) ? ;
154
let snap = repo.snapshot ( " oxy resolve" ) ? ;
155
if ! snap.base_snapshot.is_conflicted ( ) {
156
println! ( " The working copy's base has no conflicts." ) ;
157
return Ok ( 0 ) ;
158
}
159
let mut remaining = 0 ;
160
for ( p, f) in & snap.base_snapshot.files {
161
if ! f.is_conflicted ( ) {
162
continue ;
163
}
164
match snap.statuses.iter ( ) .find ( | s | s.path = = * p ) .map ( | s | s.status ) {
165
Some ( ChangeStatus:: Resolved) => println! ( " resolved {p} " ) ,
166
Some ( ChangeStatus:: ConflictEdited) => {
167
remaining + = 1 ;
168
println! ( " markers {p} " ) ;
169
}
170
_ => {
171
remaining + = 1 ;
172
println! ( " unresolved {p} " ) ;
173
}
174
}
175
}
176
for c in & snap.base_snapshot.tree.conflicts {
177
remaining + = 1 ;
178
println! ( " tree {c:?} " ) ;
179
}
180
if remaining = = 0 {
181
println! ( " All conflicts are resolved; `oxy commit` will record the resolution." ) ;
182
Ok ( 0 )
183
} else {
184
Ok ( 2 )
185
}
186
}
187
188
pub fn undo ( ) -> Result< i32 > {
189
let repo = open ( ) ? ;
190
let op = repo.undo ( ) ? ;
135212de Andy McCall 1 month ago ↶
191
println! ( " {} now at: {} " , ui:: ok( " Undone;" ) , ui:: dim( & op.command) ) ;
fce44ae9 Andy McCall 1 month ago ↶
192
Ok ( 0 )
193
}
194
195
pub fn op_log ( ) -> Result< i32 > {
196
let repo = open ( ) ? ;
197
for ( i, ( r, op) ) in repo.op_log ( ) ? .into_iter ( ) .enumerate ( ) {
135212de Andy McCall 1 month ago ↶
198
println! ( " {} {} {} {} " , if i = = 0 { ui:: ok( " @ " ) } else { " " .into ( ) } , ui:: hash( r.short ( 12 ) ) , ui:: dim( when ( op.hlc.wall_ms) ) , op.command) ;
fce44ae9 Andy McCall 1 month ago ↶
199
}
200
Ok ( 0 )
201
}
d02e545d Andy McCall 1 month ago ↶
202
ba160f60 Andy McCall 1 month ago ↶
203
pub fn gc ( now : bool ) -> Result< i32 > {
d02e545d Andy McCall 1 month ago ↶
204
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
205
let sp = ui:: spinner( " Packing and pruning…" ) ;
ba160f60 Andy McCall 1 month ago ↶
206
let r = repo.gc ( now) ? ;
135212de Andy McCall 1 month ago ↶
207
sp.clear ( ) ;
ba160f60 Andy McCall 1 month ago ↶
208
if r.packed.objects > 0 {
d02e545d Andy McCall 1 month ago ↶
209
println! (
ba160f60 Andy McCall 1 month ago ↶
210
" Packed {} : {} bytes -> {} bytes ({:.0} %)." ,
211
plural ( r.packed.objects, " loose object" ) ,
212
r.packed.raw_bytes,
213
r.packed.packed_bytes,
214
100. 0 * r.packed.packed_bytes as f64 / r.packed.raw_bytes.max ( 1 ) as f64
d02e545d Andy McCall 1 month ago ↶
215
) ;
216
}
135212de Andy McCall 1 month ago ↶
217
println! ( " {} reachable; pruned {} ({} bytes){} {} ; {} present." ,
ba160f60 Andy McCall 1 month ago ↶
218
plural ( r.reachable, " object" ) ,
219
plural ( r.pruned.objects, " unreachable object" ) ,
220
r.pruned.bytes,
221
if r.pruned.deferred > 0 { format! ( " ; {} deferred by the grace period (use --now)" , plural ( r.pruned.deferred, " object" ) ) } else { String :: new( ) } ,
135212de Andy McCall 1 month ago ↶
222
if r.merged_packs > 0 { format! ( " ; merged {} into one" , plural ( r.merged_packs, " pack" ) ) } else { String :: new( ) } ,
ba160f60 Andy McCall 1 month ago ↶
223
plural ( repo.store ( ) .pack_count ( ) , " pack" )
224
) ;
d02e545d Andy McCall 1 month ago ↶
225
Ok ( 0 )
226
}
614541c3 Andy McCall 1 month ago ↶
227
228
229
fn current_head ( repo : & Repo) -> Result< oxy_core:: hash:: ChangeHash> {
230
let snap = repo.snapshot ( " oxy" ) ? ;
231
let base = snap.base.maximal ( & snap.wc.deps) ;
232
match base.as_slice ( ) {
233
[ h] => Ok ( * h) ,
234
_ => anyhow:: bail! ( " the working copy sits on {} heads; name the change explicitly" , base.len ( ) ) ,
235
}
236
}
237
238
pub fn describe ( message : & str , change : Option< & str > ) -> Result< i32 > {
239
let repo = open ( ) ? ;
240
let h = match change {
241
Some ( c) => repo.resolve_change ( c) ? ,
242
None => current_head ( & repo) ? ,
243
} ;
244
let new = repo.describe ( & h, message) ? ;
245
let c = repo.store ( ) .get_change ( & new) ? ;
135212de Andy McCall 1 month ago ↶
246
println! ( " {} {} {} {} " , ui:: ok( " Described" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , new.short ( 12 ) ) ) , message.lines ( ) .next ( ) .unwrap_or ( " " ) ) ;
614541c3 Andy McCall 1 month ago ↶
247
Ok ( 0 )
248
}
249
250
pub fn amend ( message : Option< & str > ) -> Result< i32 > {
251
let repo = open ( ) ? ;
252
let new = repo.amend ( message) ? ;
253
let c = repo.store ( ) .get_change ( & new) ? ;
135212de Andy McCall 1 month ago ↶
254
println! ( " {} {} {} {} " , ui:: ok( " Amended" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , new.short ( 12 ) ) ) , c.message.lines ( ) .next ( ) .unwrap_or ( " " ) ) ;
614541c3 Andy McCall 1 month ago ↶
255
Ok ( 0 )
256
}
257
258
pub fn abandon ( change : & str ) -> Result< i32 > {
259
let repo = open ( ) ? ;
260
let h = repo.resolve_change ( change) ? ;
261
let c = repo.store ( ) .get_change ( & h) ? ;
262
repo.abandon ( & h) ? ;
263
println! ( " Abandoned {} #{} ; its content remains on disk as uncommitted work if the working copy sat on it." , short_id ( & c) , h.short ( 12 ) ) ;
264
Ok ( 0 )
265
}
252e8cb2 Andy McCall 1 month ago ↶
266
267
pub fn checkpoint ( ) -> Result< i32 > {
268
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
269
let sp = ui:: spinner( " Writing checkpoint…" ) ;
252e8cb2 Andy McCall 1 month ago ↶
270
let ( r, changes, bytes) = repo.checkpoint ( ) ? ;
135212de Andy McCall 1 month ago ↶
271
sp.done ( & format! ( " {} {} of {} ({} bytes) for {} " , ui:: ok( " Checkpoint" ) , ui:: hash( format! ( " #{} " , r.short ( 12 ) ) ) , plural ( changes, " change" ) , bytes, ui:: id( & repo.config.bookmark) ) ) ;
272
Ok ( 0 )
273
}
274
275
276
pub fn prompt ( format : & str ) -> Result< i32 > {
277
let Ok ( repo) = Repo:: open( & std:: env:: current_dir( ) ? ) else { return Ok ( 1 ) } ;
278
let info = repo.quick_status ( ) ? ;
279
println! ( " {} " , info.format ( format) ) ;
252e8cb2 Andy McCall 1 month ago ↶
280
Ok ( 0 )
281
}
7421309f Andy McCall 1 month ago ↶
282
283
pub fn sparse ( paths : & [String]) -> Result< i32 > {
284
let mut repo = open ( ) ? ;
285
if paths.is_empty ( ) {
286
if repo.config.sparse_paths.is_empty ( ) {
287
println! ( " No sparse filter: the whole tree is materialized." ) ;
288
} else {
289
println! ( " {} " , ui:: heading( " Sparse filter" ) ) ;
290
for p in & repo.config.sparse_paths {
291
println! ( " {} " , ui:: path( p) ) ;
292
}
293
}
294
return Ok ( 0 ) ;
295
}
296
let snap = repo.snapshot ( " oxy sparse" ) ? ;
297
if ! snap.wc.tree_ops.is_empty ( ) | | ! snap.wc.edits.is_empty ( ) {
298
anyhow:: bail! ( " the working copy has changes; commit them before changing the sparse filter" ) ;
299
}
300
repo.config.set ( " filter.paths" , & paths.join ( " ," ) ) ? ;
301
repo.save_config ( ) ? ;
302
let heads = repo.bookmark_heads ( ) ? ;
303
let sp = ui:: spinner( " Re-checking out with the new filter…" ) ;
304
let r = repo.checkout ( & heads, true , " oxy sparse (checkout)" ) ? ;
305
sp.done ( & format! ( " {} {} : {} written, {} removed" , ui:: ok( " Sparse filter set to" ) , ui:: path( paths.join ( " ," ) ) , plural ( r.written, " file" ) , r.removed) ) ;
306
Ok ( 0 )
307
}
132b884b Andy McCall 1 month ago ↶
308
309
pub fn tag_create ( name : & str , changes : & [String], message : & str ) -> Result< i32 > {
310
let repo = open ( ) ? ;
311
repo.snapshot ( " oxy tag" ) ? ;
312
let heads = if changes.is_empty ( ) {
313
repo.bookmark_heads ( ) ?
314
} else {
315
let mut heads = Vec :: new( ) ;
316
for c in changes {
317
heads.extend ( repo.resolve_heads ( c) ? ) ;
318
}
319
heads
320
} ;
321
let t = repo.tag_create ( name, & heads, message) ? ;
322
println! (
323
" {} {} -> {} ({} ){} " ,
324
ui:: ok( " Tagged" ) ,
325
ui:: id( & t.name) ,
326
ui:: hash( format! ( " #{} " , t.state.short ( 12 ) ) ) ,
327
plural ( t.heads.len ( ) , " head" ) ,
328
t.signer.map ( | s | format! ( " , signed by {s} " ) ) .unwrap_or_else ( | | ui:: dim( " , unsigned" ) )
329
) ;
330
Ok ( 0 )
331
}
332
333
pub fn tag_list ( ) -> Result< i32 > {
334
let repo = open ( ) ? ;
335
let tags = repo.tags ( ) ? ;
336
if tags.is_empty ( ) {
337
println! ( " {} " , ui:: dim( " No tags." ) ) ;
338
}
339
for t in tags {
340
println! (
341
" {} {} {} {} {} " ,
342
ui:: id( & t.name) ,
343
ui:: hash( format! ( " #{} " , t.state.short ( 12 ) ) ) ,
344
ui:: dim( format! ( " ({} )" , plural ( t.heads.len ( ) , " head" ) ) ) ,
345
if t.message.is_empty ( ) { String :: new( ) } else { format! ( " {} " , t.message.lines ( ) .next ( ) .unwrap_or ( " " ) ) } ,
346
t.signer.map ( | s | ui:: dim( format! ( " [{s} ]" ) ) ) .unwrap_or_default ( )
347
) ;
348
}
349
Ok ( 0 )
350
}
4e88e774 Andy McCall 1 month ago ↶
351
352
pub fn workspace_add ( path : & std:: path:: Path, changes : & [String]) -> Result< i32 > {
353
let repo = open ( ) ? ;
354
let heads = if changes.is_empty ( ) {
355
None
356
} else {
357
let mut heads = Vec :: new( ) ;
358
for c in changes {
359
heads.extend ( repo.resolve_heads ( c) ? ) ;
360
}
361
Some ( heads)
362
} ;
363
let sp = ui:: spinner( & format! ( " Creating workspace at {} …" , path.display ( ) ) ) ;
364
let ws = repo.workspace_add ( path, heads.as_deref ( ) ) ? ;
365
sp.done ( & format! ( " {} {} (workspace {} )" , ui:: ok( " Workspace created at" ) , ui:: path( ws.root ( ) .display ( ) ) , ui:: id( ws.workspace ( ) .short ( 8 ) ) ) ) ;
366
Ok ( 0 )
367
}
368
369
pub fn workspace_list ( ) -> Result< i32 > {
370
let repo = open ( ) ? ;
371
for w in repo.workspaces ( ) ? {
372
println! (
373
" {} {} {} {} " ,
374
if w.current { ui:: ok( " *" ) } else { " " .into ( ) } ,
375
ui:: id( w.id.short ( 8 ) ) ,
376
ui:: path( w.path.display ( ) ) ,
377
w.working_copy.map ( | h | ui:: dim( format! ( " wc #{} " , h.short ( 12 ) ) ) ) .unwrap_or_else ( | | ui:: warn( " (not in view)" ) )
378
) ;
379
}
380
Ok ( 0 )
381
}
382
383
pub fn workspace_forget ( spec : & str ) -> Result< i32 > {
384
let repo = open ( ) ? ;
385
let all = repo.workspaces ( ) ? ;
386
let target = std:: fs:: canonicalize( spec) .ok ( ) ;
387
let matches: Vec< _ > = all.iter ( ) .filter ( | w | w.id.starts_with_hex ( spec) | | target.as_ref ( ) .is_some_and ( | t | * t = = w.path) ) .collect ( ) ;
388
let w = match matches.as_slice ( ) {
389
[ w] => * w,
390
[ ] => anyhow:: bail! ( " no workspace matches {spec:?}" ) ,
391
_ => anyhow:: bail! ( " {spec:?} is ambiguous" ) ,
392
} ;
393
repo.workspace_forget ( & w.id) ? ;
394
println! ( " {} workspace {} at {} ; its files were left in place." , ui:: ok( " Forgot" ) , ui:: id( w.id.short ( 8 ) ) , ui:: path( w.path.display ( ) ) ) ;
395
Ok ( 0 )
396
}
cdb5beba Andy McCall 1 month ago ↶
397
398
pub fn lock ( path : & str ) -> Result< i32 > {
399
let repo = open ( ) ? ;
400
repo.lock_path ( path) ? ;
401
println! ( " {} {} (advisory, local)" , ui:: ok( " Locked" ) , ui:: path( path) ) ;
402
Ok ( 0 )
403
}
404
405
pub fn unlock ( path : & str ) -> Result< i32 > {
406
let repo = open ( ) ? ;
407
repo.unlock_path ( path) ? ;
408
println! ( " {} {} " , ui:: ok( " Unlocked" ) , ui:: path( path) ) ;
409
Ok ( 0 )
410
}