fce44ae9 Andy McCall 1 month ago ↶
1
2
94084087 Andy McCall 1 month ago ↶
3
use super :: format:: { author_name, deps_line, deps_of, plural, short_id, short_meta_id, signature_line, signature_mark, subject_of, 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;
94084087 Andy McCall 1 month ago ↶
7
use oxy_core:: objects:: ChangeKind;
fce44ae9 Andy McCall 1 month ago ↶
8
use oxy_core:: repo:: { render_change, ChangeStatus, Repo} ;
9
use std:: path:: PathBuf;
10
11
pub fn init ( path : Option< PathBuf> ) -> Result< i32 > {
12
let path = path.unwrap_or ( std:: env:: current_dir( ) ? ) ;
13
let repo = Repo:: init( & path) ? ;
94084087 Andy McCall 1 month ago ↶
14
println! ( " {} oxy repository in {} " , ui:: done( " Initialized" ) , ui:: path( repo.root ( ) .display ( ) ) ) ;
15
println! ( " {} " , ui:: kv( " bookmark" , ui:: bookmark( & repo.config.bookmark) ) ) ;
16
println! ( " {} " , ui:: kv( " author" , format! ( " {} <{} >" , ui:: author( & repo.config.author_name) , repo.config.author_email) ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
17
Ok ( 0 )
18
}
19
20
fn status_tag ( s : ChangeStatus) -> & 'static str {
21
match s {
22
ChangeStatus:: Added => " A" ,
23
ChangeStatus:: Modified => " M" ,
24
ChangeStatus:: Removed => " D" ,
6e723b81 Andy McCall 1 month ago ↶
25
ChangeStatus:: Renamed => " R" ,
fce44ae9 Andy McCall 1 month ago ↶
26
ChangeStatus:: ModeChanged => " X" ,
27
ChangeStatus:: ConflictEdited => " C!" ,
6e723b81 Andy McCall 1 month ago ↶
28
ChangeStatus:: Resolved => " V" ,
fce44ae9 Andy McCall 1 month ago ↶
29
}
30
}
31
50a046e4 Andy McCall 1 month ago ↶
32
pub fn status ( json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
33
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
34
let sp = ui:: spinner( " Snapshotting the working copy…" ) ;
fce44ae9 Andy McCall 1 month ago ↶
35
let snap = repo.snapshot ( " oxy status" ) ? ;
36
let heads = repo.bookmark_heads ( ) ? ;
135212de Andy McCall 1 month ago ↶
37
sp.clear ( ) ;
50a046e4 Andy McCall 1 month ago ↶
38
if json {
39
println! ( " {} " , repo.status_json ( & snap) ? ) ;
40
return Ok ( if snap.base_snapshot.is_conflicted ( ) { 2 } else { 0 } ) ;
41
}
135212de Andy McCall 1 month ago ↶
42
println! (
94084087 Andy McCall 1 month ago ↶
43
" {} {} {} working copy {} {} {} " ,
44
ui:: dim( " On" ) ,
45
ui:: bookmark( & repo.config.bookmark) ,
46
ui:: sep( ) ,
135212de Andy McCall 1 month ago ↶
47
ui:: id( snap.wc.change_id.short ( 8 ) ) ,
94084087 Andy McCall 1 month ago ↶
48
ui:: sep( ) ,
49
ui:: dim( plural ( heads.len ( ) , " head" ) )
135212de Andy McCall 1 month ago ↶
50
) ;
fce44ae9 Andy McCall 1 month ago ↶
51
if snap.statuses.is_empty ( ) {
94084087 Andy McCall 1 month ago ↶
52
println! ( " {} {} " , ui:: ok( ui:: glyph( " ✔" , " *" ) ) , ui:: dim( " nothing to commit, working copy clean" ) ) ;
53
} else {
54
println! ( ) ;
55
for s in & snap.statuses {
56
println! ( " {} {} " , ui:: status_mark( status_tag ( s.status) ) , ui:: path( & s.path) ) ;
57
}
fce44ae9 Andy McCall 1 month ago ↶
58
}
59
if ! snap.base_snapshot.is_conflicted ( ) {
60
return Ok ( 0 ) ;
61
}
135212de Andy McCall 1 month ago ↶
62
println! ( ) ;
94084087 Andy McCall 1 month ago ↶
63
println! ( " {} {} " , ui:: section( ui:: err( " Conflicts" ) ) , ui:: dim( " edit the marked files, or the .oxy-conflict-N candidates, then commit" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
64
for c in & snap.base_snapshot.tree.conflicts {
94084087 Andy McCall 1 month ago ↶
65
println! ( " {} tree: {c:?} " , ui:: err( ui:: glyph( " ✱" , " !" ) ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
66
}
67
for ( p, f) in & snap.base_snapshot.files {
68
if f.is_conflicted ( ) {
69
let state = match snap.statuses.iter ( ) .find ( | s | s.path = = * p ) .map ( | s | s.status ) {
94084087 Andy McCall 1 month ago ↶
70
Some ( ChangeStatus:: Resolved) => ui:: ok( format! ( " {} resolved in working copy" , ui:: glyph( " ✔" , " *" ) ) ) ,
71
Some ( ChangeStatus:: ConflictEdited) => ui:: warn( format! ( " {} markers remain" , ui:: glyph( " ‼" , " !" ) ) ) ,
72
_ => ui:: err( format! ( " {} unresolved" , ui:: glyph( " ✱" , " !" ) ) ) ,
fce44ae9 Andy McCall 1 month ago ↶
73
} ;
135212de Andy McCall 1 month ago ↶
74
println! ( " {} {state} " , ui:: path( p) ) ;
fce44ae9 Andy McCall 1 month ago ↶
75
}
76
}
77
Ok ( 2 )
78
}
79
957fee30 Andy McCall 1 month ago ↶
80
pub fn commit ( message : & str , depends_on : & [String], paths : & [String], interactive : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
81
let repo = open ( ) ? ;
82
let mut deps = Vec :: new( ) ;
83
for d in depends_on {
84
deps.push ( repo.resolve_change ( d) ? ) ;
85
}
957fee30 Andy McCall 1 month ago ↶
86
let selection = if interactive {
87
let ( _ , hunks) = repo.hunks ( ) ? ;
88
if hunks.is_empty ( ) {
89
anyhow:: bail! ( " nothing to commit: the working copy matches its base" ) ;
90
}
91
let hunks: Vec< _ > = hunks.into_iter ( ) .filter ( | h | paths.is_empty ( ) | | paths.iter ( ) .any ( | p | h.path = = * p | | h.path.starts_with ( & format! ( " {} /" , p.trim_end_matches ( ' /' ) ) ) ) ) .collect ( ) ;
92
let mut chosen = Vec :: new( ) ;
93
let stdin = std:: io:: stdin( ) ;
94
let mut take_rest = false ;
95
for ( n, h) in hunks.iter ( ) .enumerate ( ) {
96
if take_rest {
97
chosen.push ( h.index) ;
98
continue ;
99
}
100
println! ( " {} {} {} " , ui:: section( format! ( " hunk {} of {} " , n + 1 , hunks.len ( ) ) ) , ui:: path( & h.path) , ui:: dim( format! ( " (#{} )" , h.index) ) ) ;
101
for line in h.text.lines ( ) {
102
let styled = if line.starts_with ( ' +' ) { ui:: ok( line) } else if line.starts_with ( ' -' ) { ui:: err( line) } else { ui:: dim( line) } ;
103
println! ( " {styled} " ) ;
104
}
105
loop {
106
print! ( " {} " , ui:: warn( " Commit this hunk? [y]es [n]o [a]ll remaining [q]uit:" ) ) ;
107
use std:: io:: Write as _ ;
108
std:: io:: stdout( ) .flush ( ) ? ;
109
let mut answer = String :: new( ) ;
110
if stdin.read_line ( & mut answer) ? = = 0 {
111
anyhow:: bail! ( " aborted" ) ;
112
}
113
match answer.trim ( ) {
114
" y" | " Y" => {
115
chosen.push ( h.index) ;
116
break ;
117
}
118
" n" | " N" | " " => break ,
119
" a" | " A" => {
120
chosen.push ( h.index) ;
121
take_rest = true ;
122
break ;
123
}
124
" q" | " Q" => anyhow:: bail! ( " aborted; nothing committed" ) ,
125
_ => continue ,
126
}
127
}
128
}
129
if chosen.is_empty ( ) {
130
anyhow:: bail! ( " no hunks chosen; nothing committed" ) ;
131
}
132
oxy_core:: repo:: Selection:: Hunks( chosen)
133
} else if paths.is_empty ( ) {
134
oxy_core:: repo:: Selection:: All
135
} else {
136
oxy_core:: repo:: Selection:: Paths( paths.to_vec ( ) )
137
} ;
135212de Andy McCall 1 month ago ↶
138
let sp = ui:: spinner( " Committing…" ) ;
957fee30 Andy McCall 1 month ago ↶
139
let h = repo.commit_selection ( message, & deps, & selection) ? ;
fce44ae9 Andy McCall 1 month ago ↶
140
let c = repo.store ( ) .get_change ( & h) ? ;
94084087 Andy McCall 1 month ago ↶
141
sp.done ( & format! ( " {} {} {} {} " , ui:: done( " Committed" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , h.short ( 12 ) ) ) , ui:: subject( subject_of ( message) ) ) ) ;
957fee30 Andy McCall 1 month ago ↶
142
if ! matches! ( selection, oxy_core:: repo:: Selection:: All) {
143
let rest = repo.snapshot ( " oxy commit" ) ? ;
144
if ! rest.statuses.is_empty ( ) {
145
println! ( " {} {} " , ui:: note( ) , ui:: dim( format! ( " {} left uncommitted" , plural ( rest.statuses.len ( ) , " path" ) ) ) ) ;
146
}
147
}
148
Ok ( 0 )
149
}
150
151
pub fn blame ( path : & str , json : bool ) -> Result< i32 > {
152
let repo = open ( ) ? ;
153
let lines = repo.blame ( path) ? ;
154
if json {
155
let items = lines.iter ( ) .map ( | l | {
156
format! (
157
" {{ \" line\" :{} ,\" change_id\" :{} ,\" hash\" :{} ,\" index\" :{} ,\" author\" :{} ,\" when_ms\" :{} }} " ,
158
l.line,
159
oxy_core:: repo:: json_str( & l.change_id.to_hex ( ) ) ,
160
oxy_core:: repo:: json_str( & l.hash.to_hex ( ) ) ,
161
l.index,
162
oxy_core:: repo:: json_str( & l.author) ,
163
l.when_ms
164
)
165
} ) ;
166
println! ( " {} " , oxy_core:: repo:: json_array( items) ) ;
167
return Ok ( 0 ) ;
168
}
169
let width = lines.iter ( ) .map ( | l | l.author.chars ( ) .count ( ) ) .max ( ) .unwrap_or ( 0 ) .min ( 20 ) ;
170
let digits = lines.len ( ) .to_string ( ) .len ( ) ;
171
for l in & lines {
172
let author: String = l.author.chars ( ) .take ( 20 ) .collect ( ) ;
173
let text = String :: from_utf8_lossy( & l.text) ;
174
println! (
175
" {} {} {} {:>digits$}{} {} " ,
176
ui:: id( l.change_id.short ( 8 ) ) ,
177
ui:: author( format! ( " {author:<width$}" ) ) ,
178
ui:: dim( ui:: ago( l.when_ms) ) ,
179
l.line,
180
ui:: dim( ui:: glyph( " │" , " |" ) ) ,
181
text.trim_end_matches ( ' \n ' )
182
) ;
183
}
184
Ok ( 0 )
185
}
186
187
fn bisect_report ( repo : & Repo, step : & oxy_core:: repo:: BisectStep) -> Result< i32 > {
188
use oxy_core:: repo:: BisectStep:: * ;
189
match step {
190
NeedMarks { good, bad } => {
191
println! ( " {} {} {} " , ui:: note( ) , ui:: dim( format! ( " {good} good, {bad} bad;" ) ) , ui:: dim( if * good = = 0 { " mark a known-good change or bookmark: oxy bisect good CHANGE" } else { " mark a known-bad change or bookmark: oxy bisect bad CHANGE" } ) ) ;
192
Ok ( 0 )
193
}
194
Testing { heads, remaining, steps_left } => {
195
println! ( " {} {} {} " , ui:: done( " Testing" ) , ui:: dim( format! ( " {} in the state under test;" , plural ( heads.len ( ) , " head" ) ) ) , ui:: dim( format! ( " {remaining} candidates left, about {steps_left} more steps" ) ) ) ;
196
println! ( " {} {} " , ui:: note( ) , ui:: dim( " run your test, then oxy bisect good | bad | skip" ) ) ;
197
Ok ( 0 )
198
}
199
Found { culprit } => {
200
let c = repo.store ( ) .get_change ( culprit) ? ;
201
println! ( " {} {} {} {} " , ui:: done( " Found" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , culprit.short ( 12 ) ) ) , ui:: subject( subject_of ( & c.message) ) ) ;
202
println! ( " {} {} " , ui:: note( ) , ui:: dim( " the first change whose inclusion makes the state bad; oxy bisect reset to return" ) ) ;
203
Ok ( 0 )
204
}
205
Inconclusive { candidates } => {
206
println! ( " {} {} " , ui:: caution( ) , ui:: warn( format! ( " every remaining candidate was skipped ({} ); oxy bisect reset to return" , plural ( candidates.len ( ) , " change" ) ) ) ) ;
207
Ok ( 1 )
208
}
209
}
210
}
211
212
pub fn bisect_start ( ) -> Result< i32 > {
213
let repo = open ( ) ? ;
214
repo.bisect_start ( ) ? ;
215
println! ( " {} bisect; {} " , ui:: done( " Started" ) , ui:: dim( " now: oxy bisect good CHANGE and oxy bisect bad CHANGE" ) ) ;
216
Ok ( 0 )
217
}
218
219
pub fn bisect_mark ( good : Option< bool > , changes : & [String]) -> Result< i32 > {
220
let repo = open ( ) ? ;
221
let mut heads = Vec :: new( ) ;
222
for c in changes {
223
heads.extend ( repo.resolve_heads ( c) ? ) ;
224
}
225
let step = repo.bisect_mark ( good, & heads) ? ;
226
bisect_report ( & repo, & step)
227
}
228
229
pub fn bisect_run ( command : & [String]) -> Result< i32 > {
230
let repo = open ( ) ? ;
231
let step = repo.bisect_run ( command) ? ;
232
bisect_report ( & repo, & step)
233
}
234
235
pub fn bisect_status ( ) -> Result< i32 > {
236
let repo = open ( ) ? ;
237
match repo.bisect_status ( ) ? {
238
None => {
239
println! ( " {} " , ui:: dim( " No bisect in progress." ) ) ;
240
Ok ( 0 )
241
}
242
Some ( step) => bisect_report ( & repo, & step) ,
243
}
244
}
245
246
pub fn bisect_reset ( ) -> Result< i32 > {
247
let repo = open ( ) ? ;
248
repo.bisect_reset ( ) ? ;
249
println! ( " {} bisect; working copy restored" , ui:: done( " Reset" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
250
Ok ( 0 )
251
}
252
94084087 Andy McCall 1 month ago ↶
253
254
255
256
pub fn log ( limit : Option< usize > , verbose : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
257
let repo = open ( ) ? ;
94084087 Andy McCall 1 month ago ↶
258
let snap = repo.snapshot ( " oxy log" ) ? ;
259
let conflicted = snap.base_snapshot.is_conflicted ( ) ;
260
let entries = repo.log ( ) ? ;
261
let total = entries.len ( ) ;
262
let shown = limit.unwrap_or ( total) .min ( total) ;
263
let rail = ui:: rail( ) ;
264
for ( i, e) in entries.iter ( ) .take ( shown) .enumerate ( ) {
fce44ae9 Andy McCall 1 month ago ↶
265
let c = & e.change;
94084087 Andy McCall 1 month ago ↶
266
let bullet = ui:: log_bullet( e.is_working_copy, e.is_head, e.is_working_copy & & conflicted) ;
267
let mut labels = Vec :: new( ) ;
fce44ae9 Andy McCall 1 month ago ↶
268
if e.is_working_copy {
94084087 Andy McCall 1 month ago ↶
269
labels.push ( ui:: warn( " working copy" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
270
}
94084087 Andy McCall 1 month ago ↶
271
if c.kind = = ChangeKind:: Resolution {
272
labels.push ( ui:: warn( " resolution" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
273
}
94084087 Andy McCall 1 month ago ↶
274
let labels = if labels.is_empty ( ) { String :: new( ) } else { format! ( " {} {} {} " , ui:: dim( " (" ) , labels.join ( ui:: dim( " , " ) .as_str ( ) ) , ui:: dim( " )" ) ) } ;
275
let subject = if e.is_working_copy & & c.message.is_empty ( ) {
276
ui:: dim( if snap.statuses.is_empty ( ) { " (clean)" } else { " (uncommitted changes)" } )
277
} else {
278
ui:: subject( subject_of ( & c.message) )
279
} ;
280
println! ( " {bullet} {} {labels} {subject} " , ui:: id( short_meta_id ( c) ) ) ;
281
let meta = format! (
282
" {} {} {} {} {} {} {} " ,
283
ui:: hash( format! ( " #{} " , e.hash.short ( 12 ) ) ) ,
284
ui:: sep( ) ,
285
ui:: author( author_name ( & repo, c) ) ,
286
ui:: sep( ) ,
287
ui:: dim( ui:: ago( c.hlc.wall_ms) ) ,
288
ui:: sep( ) ,
289
if e.is_working_copy { ui:: dim( " not yet committed" ) } else { signature_mark ( & repo, & e.hash) ? }
290
) ;
291
println! ( " {rail} {meta} " ) ;
292
if verbose {
293
if ! e.is_working_copy {
294
println! ( " {rail} {} " , ui:: dim( format! ( " {} {} " , when ( c.hlc.wall_ms) , signature_line ( & repo, & e.hash) ? ) ) ) ;
295
}
296
if ! c.deps.is_empty ( ) {
297
println! ( " {rail} {} {} " , ui:: dim( ui:: glyph( " ↳" , " ->" ) ) , ui:: dim( deps_of ( & c.deps) ) ) ;
298
}
299
let body: Vec< & str > = c.message.lines ( ) .skip ( 1 ) .skip_while ( | l | l.trim ( ) .is_empty ( ) ) .collect ( ) ;
300
if ! body.is_empty ( ) {
301
println! ( " {rail} " ) ;
302
for line in body {
303
println! ( " {rail} {line} " ) ;
304
}
305
}
fce44ae9 Andy McCall 1 month ago ↶
306
}
94084087 Andy McCall 1 month ago ↶
307
if i + 1 < shown {
308
println! ( " {rail} " ) ;
fce44ae9 Andy McCall 1 month ago ↶
309
}
310
}
94084087 Andy McCall 1 month ago ↶
311
if shown < total {
312
println! ( " {} " , ui:: dim( format! ( " {} {} more; pass -n to see them" , ui:: glyph( " …" , " ..." ) , total - shown) ) ) ;
313
}
fce44ae9 Andy McCall 1 month ago ↶
314
Ok ( 0 )
315
}
316
317
pub fn diff ( ) -> Result< i32 > {
318
let repo = open ( ) ? ;
319
let snap = repo.snapshot ( " oxy diff" ) ? ;
320
print! ( " {} " , render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ) ;
321
Ok ( 0 )
322
}
323
324
pub fn show ( change : & str ) -> Result< i32 > {
325
let repo = open ( ) ? ;
326
repo.snapshot ( " oxy show" ) ? ;
327
let h = repo.resolve_change ( change) ? ;
328
let c = repo.store ( ) .get_change ( & h) ? ;
94084087 Andy McCall 1 month ago ↶
329
println! ( " {} {} {} " , ui:: section( " change" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{h} " ) ) ) ;
330
println! ( " {} " , ui:: kv( " kind" , format! ( " {:?} " , c.kind) .to_lowercase ( ) ) ) ;
331
println! ( " {} " , ui:: kv( " author" , ui:: author( author_name ( & repo, & oxy_core:: state:: ChangeMeta:: of( & c) ) ) ) ) ;
332
println! ( " {} " , ui:: kv( " date" , format! ( " {} {} " , when ( c.hlc.wall_ms) , ui:: dim( format! ( " ({} )" , ui:: ago( c.hlc.wall_ms) ) ) ) ) ) ;
333
println! ( " {} " , ui:: kv( " deps" , ui:: dim( deps_line ( & c) ) ) ) ;
334
println! ( " {} " , ui:: kv( " sig" , signature_line ( & repo, & h) ? ) ) ;
335
println! ( ) ;
336
for line in ( if c.message.is_empty ( ) { " (no description)" } else { & c.message } ) .lines ( ) {
337
println! ( " {line} " ) ;
338
}
339
println! ( ) ;
fce44ae9 Andy McCall 1 month ago ↶
340
print! ( " {} " , repo.describe_change ( & h) ? ) ;
341
Ok ( 0 )
342
}
343
344
pub fn checkout ( changes : & [String], discard : bool ) -> Result< i32 > {
345
let repo = open ( ) ? ;
346
let heads = if changes.is_empty ( ) {
347
repo.bookmark_heads ( ) ?
348
} else {
132b884b Andy McCall 1 month ago ↶
349
let mut heads = Vec :: new( ) ;
350
for c in changes {
351
heads.extend ( repo.resolve_heads ( c) ? ) ;
352
}
353
heads
fce44ae9 Andy McCall 1 month ago ↶
354
} ;
355
let spec = if changes.is_empty ( ) { repo.config.bookmark.clone ( ) } else { changes.join ( " " ) } ;
135212de Andy McCall 1 month ago ↶
356
let sp = ui:: spinner( & format! ( " Checking out {spec} …" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
357
let r = repo.checkout ( & heads, discard, & format! ( " oxy checkout {spec} " ) ) ? ;
94084087 Andy McCall 1 month ago ↶
358
sp.done ( & format! ( " {} on {} : {} written, {} removed" , ui:: done( " Working copy" ) , ui:: bookmark( & spec) , plural ( r.written, " file" ) , r.removed) ) ;
4dee277c Andy McCall 1 month ago ↶
359
for n in & r.notes {
360
println! ( " {} {n} " , ui:: note( ) ) ;
361
}
fce44ae9 Andy McCall 1 month ago ↶
362
if r.conflicted {
94084087 Andy McCall 1 month ago ↶
363
println! ( " {} {} " , ui:: caution( ) , ui:: warn( " this state has conflicts; see oxy status" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
364
}
365
Ok ( 0 )
366
}
367
368
pub fn resolve ( ) -> Result< i32 > {
369
let repo = open ( ) ? ;
370
let snap = repo.snapshot ( " oxy resolve" ) ? ;
371
if ! snap.base_snapshot.is_conflicted ( ) {
372
println! ( " The working copy's base has no conflicts." ) ;
373
return Ok ( 0 ) ;
374
}
375
let mut remaining = 0 ;
376
for ( p, f) in & snap.base_snapshot.files {
377
if ! f.is_conflicted ( ) {
378
continue ;
379
}
380
match snap.statuses.iter ( ) .find ( | s | s.path = = * p ) .map ( | s | s.status ) {
381
Some ( ChangeStatus:: Resolved) => println! ( " resolved {p} " ) ,
382
Some ( ChangeStatus:: ConflictEdited) => {
383
remaining + = 1 ;
384
println! ( " markers {p} " ) ;
385
}
386
_ => {
387
remaining + = 1 ;
388
println! ( " unresolved {p} " ) ;
389
}
390
}
391
}
392
for c in & snap.base_snapshot.tree.conflicts {
393
remaining + = 1 ;
394
println! ( " tree {c:?} " ) ;
395
}
396
if remaining = = 0 {
397
println! ( " All conflicts are resolved; `oxy commit` will record the resolution." ) ;
398
Ok ( 0 )
399
} else {
400
Ok ( 2 )
401
}
402
}
403
404
pub fn undo ( ) -> Result< i32 > {
405
let repo = open ( ) ? ;
406
let op = repo.undo ( ) ? ;
94084087 Andy McCall 1 month ago ↶
407
println! ( " {} now at: {} " , ui:: done( " Undone;" ) , ui:: dim( & op.command) ) ;
fce44ae9 Andy McCall 1 month ago ↶
408
Ok ( 0 )
409
}
410
411
pub fn op_log ( ) -> Result< i32 > {
412
let repo = open ( ) ? ;
413
for ( i, ( r, op) ) in repo.op_log ( ) ? .into_iter ( ) .enumerate ( ) {
94084087 Andy McCall 1 month ago ↶
414
let bullet = if i = = 0 { ui:: ok( ui:: glyph( " ●" , " @" ) ) } else { ui:: dim( ui:: glyph( " ○" , " o" ) ) } ;
415
let cmd = subject_of ( & op.command) .split ( " \\ n" ) .next ( ) .unwrap_or ( " " ) ;
416
println! ( " {bullet} {} {} {} {} " , ui:: hash( r.short ( 12 ) ) , ui:: sep( ) , ui:: dim( format! ( " {:<16}" , ui:: ago( op.hlc.wall_ms) ) ) , if i = = 0 { ui:: subject( cmd) } else { cmd.to_string ( ) } ) ;
fce44ae9 Andy McCall 1 month ago ↶
417
}
418
Ok ( 0 )
419
}
d02e545d Andy McCall 1 month ago ↶
420
ba160f60 Andy McCall 1 month ago ↶
421
pub fn gc ( now : bool ) -> Result< i32 > {
d02e545d Andy McCall 1 month ago ↶
422
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
423
let sp = ui:: spinner( " Packing and pruning…" ) ;
ba160f60 Andy McCall 1 month ago ↶
424
let r = repo.gc ( now) ? ;
135212de Andy McCall 1 month ago ↶
425
sp.clear ( ) ;
ba160f60 Andy McCall 1 month ago ↶
426
if r.packed.objects > 0 {
d02e545d Andy McCall 1 month ago ↶
427
println! (
ba160f60 Andy McCall 1 month ago ↶
428
" Packed {} : {} bytes -> {} bytes ({:.0} %)." ,
429
plural ( r.packed.objects, " loose object" ) ,
430
r.packed.raw_bytes,
431
r.packed.packed_bytes,
432
100. 0 * r.packed.packed_bytes as f64 / r.packed.raw_bytes.max ( 1 ) as f64
d02e545d Andy McCall 1 month ago ↶
433
) ;
434
}
135212de Andy McCall 1 month ago ↶
435
println! ( " {} reachable; pruned {} ({} bytes){} {} ; {} present." ,
ba160f60 Andy McCall 1 month ago ↶
436
plural ( r.reachable, " object" ) ,
437
plural ( r.pruned.objects, " unreachable object" ) ,
438
r.pruned.bytes,
439
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 ↶
440
if r.merged_packs > 0 { format! ( " ; merged {} into one" , plural ( r.merged_packs, " pack" ) ) } else { String :: new( ) } ,
ba160f60 Andy McCall 1 month ago ↶
441
plural ( repo.store ( ) .pack_count ( ) , " pack" )
442
) ;
d02e545d Andy McCall 1 month ago ↶
443
Ok ( 0 )
444
}
614541c3 Andy McCall 1 month ago ↶
445
446
447
fn current_head ( repo : & Repo) -> Result< oxy_core:: hash:: ChangeHash> {
448
let snap = repo.snapshot ( " oxy" ) ? ;
449
let base = snap.base.maximal ( & snap.wc.deps) ;
450
match base.as_slice ( ) {
451
[ h] => Ok ( * h) ,
452
_ => anyhow:: bail! ( " the working copy sits on {} heads; name the change explicitly" , base.len ( ) ) ,
453
}
454
}
455
456
pub fn describe ( message : & str , change : Option< & str > ) -> Result< i32 > {
457
let repo = open ( ) ? ;
458
let h = match change {
459
Some ( c) => repo.resolve_change ( c) ? ,
460
None => current_head ( & repo) ? ,
461
} ;
462
let new = repo.describe ( & h, message) ? ;
463
let c = repo.store ( ) .get_change ( & new) ? ;
94084087 Andy McCall 1 month ago ↶
464
println! ( " {} {} {} {} " , ui:: done( " Described" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , new.short ( 12 ) ) ) , ui:: subject( subject_of ( message) ) ) ;
614541c3 Andy McCall 1 month ago ↶
465
Ok ( 0 )
466
}
467
23b217d7 Andy McCall 1 month ago ↶
468
pub fn amend ( message : Option< & str > , into : Option< & str > ) -> Result< i32 > {
614541c3 Andy McCall 1 month ago ↶
469
let repo = open ( ) ? ;
23b217d7 Andy McCall 1 month ago ↶
470
let new = match into {
471
Some ( spec) => {
472
let target = repo.resolve_change ( spec) ? ;
473
repo.amend_into ( & target, message) ?
474
}
475
None => repo.amend ( message) ? ,
476
} ;
614541c3 Andy McCall 1 month ago ↶
477
let c = repo.store ( ) .get_change ( & new) ? ;
94084087 Andy McCall 1 month ago ↶
478
println! ( " {} {} {} {} " , ui:: done( " Amended" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , new.short ( 12 ) ) ) , ui:: subject( subject_of ( & c.message) ) ) ;
614541c3 Andy McCall 1 month ago ↶
479
Ok ( 0 )
480
}
481
482
pub fn abandon ( change : & str ) -> Result< i32 > {
483
let repo = open ( ) ? ;
484
let h = repo.resolve_change ( change) ? ;
485
let c = repo.store ( ) .get_change ( & h) ? ;
6706bdcf Andy McCall 1 month ago ↶
486
let notes = repo.abandon ( & h) ? ;
94084087 Andy McCall 1 month ago ↶
487
println! ( " {} {} {} {} " , ui:: done( " Abandoned" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , h.short ( 12 ) ) ) , ui:: dim( " its content remains on disk as uncommitted work if the working copy sat on it" ) ) ;
6706bdcf Andy McCall 1 month ago ↶
488
for n in notes {
94084087 Andy McCall 1 month ago ↶
489
println! ( " {} {n} " , ui:: note( ) ) ;
6706bdcf Andy McCall 1 month ago ↶
490
}
614541c3 Andy McCall 1 month ago ↶
491
Ok ( 0 )
492
}
252e8cb2 Andy McCall 1 month ago ↶
493
494
pub fn checkpoint ( ) -> Result< i32 > {
495
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
496
let sp = ui:: spinner( " Writing checkpoint…" ) ;
252e8cb2 Andy McCall 1 month ago ↶
497
let ( r, changes, bytes) = repo.checkpoint ( ) ? ;
94084087 Andy McCall 1 month ago ↶
498
sp.done ( & format! ( " {} {} of {} ({} bytes) for {} " , ui:: done( " Checkpoint" ) , ui:: hash( format! ( " #{} " , r.short ( 12 ) ) ) , plural ( changes, " change" ) , bytes, ui:: id( & repo.config.bookmark) ) ) ;
135212de Andy McCall 1 month ago ↶
499
Ok ( 0 )
500
}
501
502
694805cc Andy McCall 1 month ago ↶
503
pub fn prompt ( format : & str , glyphs : bool , color : Option< & str > ) -> Result< i32 > {
135212de Andy McCall 1 month ago ↶
504
let Ok ( repo) = Repo:: open( & std:: env:: current_dir( ) ? ) else { return Ok ( 1 ) } ;
505
let info = repo.quick_status ( ) ? ;
694805cc Andy McCall 1 month ago ↶
506
let fmt = if glyphs & & format = = oxy_core:: repo:: DEFAULT_PROMPT_FORMAT { oxy_core:: repo:: GLYPH_PROMPT_FORMAT } else { format } ;
507
let line = match color {
508
Some ( " bash" ) => info.format_colored ( fmt, ( " \\ [" , " \\ ]" ) ) ,
509
Some ( " zsh" ) => info.format_colored ( fmt, ( " %{" , " %}" ) ) ,
510
Some ( _ ) => info.format_colored ( fmt, ( " " , " " ) ) ,
511
None => info.format ( fmt) ,
512
} ;
513
println! ( " {line} " ) ;
252e8cb2 Andy McCall 1 month ago ↶
514
Ok ( 0 )
515
}
7421309f Andy McCall 1 month ago ↶
516
517
pub fn sparse ( paths : & [String]) -> Result< i32 > {
518
let mut repo = open ( ) ? ;
519
if paths.is_empty ( ) {
520
if repo.config.sparse_paths.is_empty ( ) {
521
println! ( " No sparse filter: the whole tree is materialized." ) ;
522
} else {
523
println! ( " {} " , ui:: heading( " Sparse filter" ) ) ;
524
for p in & repo.config.sparse_paths {
525
println! ( " {} " , ui:: path( p) ) ;
526
}
527
}
528
return Ok ( 0 ) ;
529
}
530
let snap = repo.snapshot ( " oxy sparse" ) ? ;
531
if ! snap.wc.tree_ops.is_empty ( ) | | ! snap.wc.edits.is_empty ( ) {
532
anyhow:: bail! ( " the working copy has changes; commit them before changing the sparse filter" ) ;
533
}
534
repo.config.set ( " filter.paths" , & paths.join ( " ," ) ) ? ;
535
repo.save_config ( ) ? ;
536
let heads = repo.bookmark_heads ( ) ? ;
537
let sp = ui:: spinner( " Re-checking out with the new filter…" ) ;
538
let r = repo.checkout ( & heads, true , " oxy sparse (checkout)" ) ? ;
539
sp.done ( & format! ( " {} {} : {} written, {} removed" , ui:: ok( " Sparse filter set to" ) , ui:: path( paths.join ( " ," ) ) , plural ( r.written, " file" ) , r.removed) ) ;
540
Ok ( 0 )
541
}
132b884b Andy McCall 1 month ago ↶
542
543
pub fn tag_create ( name : & str , changes : & [String], message : & str ) -> Result< i32 > {
544
let repo = open ( ) ? ;
545
repo.snapshot ( " oxy tag" ) ? ;
546
let heads = if changes.is_empty ( ) {
547
repo.bookmark_heads ( ) ?
548
} else {
549
let mut heads = Vec :: new( ) ;
550
for c in changes {
551
heads.extend ( repo.resolve_heads ( c) ? ) ;
552
}
553
heads
554
} ;
555
let t = repo.tag_create ( name, & heads, message) ? ;
556
println! (
94084087 Andy McCall 1 month ago ↶
557
" {} {} {} {} ({} ){} " ,
558
ui:: done( " Tagged" ) ,
559
ui:: bookmark( & t.name) ,
560
ui:: dim( ui:: glyph( " →" , " ->" ) ) ,
132b884b Andy McCall 1 month ago ↶
561
ui:: hash( format! ( " #{} " , t.state.short ( 12 ) ) ) ,
562
plural ( t.heads.len ( ) , " head" ) ,
563
t.signer.map ( | s | format! ( " , signed by {s} " ) ) .unwrap_or_else ( | | ui:: dim( " , unsigned" ) )
564
) ;
565
Ok ( 0 )
566
}
567
568
pub fn tag_list ( ) -> Result< i32 > {
569
let repo = open ( ) ? ;
570
let tags = repo.tags ( ) ? ;
571
if tags.is_empty ( ) {
572
println! ( " {} " , ui:: dim( " No tags." ) ) ;
573
}
574
for t in tags {
575
println! (
94084087 Andy McCall 1 month ago ↶
576
" {} {} {} {} {} {} " ,
577
ui:: ok( ui:: glyph( " ⚑" , " #" ) ) ,
578
ui:: bookmark( & t.name) ,
132b884b Andy McCall 1 month ago ↶
579
ui:: hash( format! ( " #{} " , t.state.short ( 12 ) ) ) ,
94084087 Andy McCall 1 month ago ↶
580
ui:: sep( ) + & ui:: dim( plural ( t.heads.len ( ) , " head" ) ) ,
581
if t.message.is_empty ( ) { String :: new( ) } else { format! ( " {} " , ui:: subject( subject_of ( & t.message) ) ) } ,
582
t.signer.map ( | s | ui:: sep( ) + & ui:: ok( format! ( " {} {s} " , ui:: glyph( " ✔" , " v" ) ) ) ) .unwrap_or_default ( )
132b884b Andy McCall 1 month ago ↶
583
) ;
584
}
585
Ok ( 0 )
586
}
4e88e774 Andy McCall 1 month ago ↶
587
588
pub fn workspace_add ( path : & std:: path:: Path, changes : & [String]) -> Result< i32 > {
589
let repo = open ( ) ? ;
590
let heads = if changes.is_empty ( ) {
591
None
592
} else {
593
let mut heads = Vec :: new( ) ;
594
for c in changes {
595
heads.extend ( repo.resolve_heads ( c) ? ) ;
596
}
597
Some ( heads)
598
} ;
599
let sp = ui:: spinner( & format! ( " Creating workspace at {} …" , path.display ( ) ) ) ;
600
let ws = repo.workspace_add ( path, heads.as_deref ( ) ) ? ;
94084087 Andy McCall 1 month ago ↶
601
sp.done ( & format! ( " {} {} (workspace {} )" , ui:: done( " Workspace created at" ) , ui:: path( ws.root ( ) .display ( ) ) , ui:: id( ws.workspace ( ) .short ( 8 ) ) ) ) ;
4e88e774 Andy McCall 1 month ago ↶
602
Ok ( 0 )
603
}
604
605
pub fn workspace_list ( ) -> Result< i32 > {
606
let repo = open ( ) ? ;
607
for w in repo.workspaces ( ) ? {
608
println! (
609
" {} {} {} {} " ,
94084087 Andy McCall 1 month ago ↶
610
if w.current { ui:: ok( ui:: glyph( " ●" , " *" ) ) } else { ui:: dim( ui:: glyph( " ○" , " o" ) ) } ,
4e88e774 Andy McCall 1 month ago ↶
611
ui:: id( w.id.short ( 8 ) ) ,
612
ui:: path( w.path.display ( ) ) ,
94084087 Andy McCall 1 month ago ↶
613
w.working_copy.map ( | h | ui:: sep( ) + & ui:: dim( format! ( " working copy #{} " , h.short ( 12 ) ) ) ) .unwrap_or_else ( | | ui:: sep( ) + & ui:: warn( " not in view" ) )
4e88e774 Andy McCall 1 month ago ↶
614
) ;
615
}
616
Ok ( 0 )
617
}
618
619
pub fn workspace_forget ( spec : & str ) -> Result< i32 > {
620
let repo = open ( ) ? ;
621
let all = repo.workspaces ( ) ? ;
622
let target = std:: fs:: canonicalize( spec) .ok ( ) ;
623
let matches: Vec< _ > = all.iter ( ) .filter ( | w | w.id.starts_with_hex ( spec) | | target.as_ref ( ) .is_some_and ( | t | * t = = w.path) ) .collect ( ) ;
624
let w = match matches.as_slice ( ) {
625
[ w] => * w,
626
[ ] => anyhow:: bail! ( " no workspace matches {spec:?}" ) ,
627
_ => anyhow:: bail! ( " {spec:?} is ambiguous" ) ,
628
} ;
629
repo.workspace_forget ( & w.id) ? ;
94084087 Andy McCall 1 month ago ↶
630
println! ( " {} workspace {} at {} ; its files were left in place." , ui:: done( " Forgot" ) , ui:: id( w.id.short ( 8 ) ) , ui:: path( w.path.display ( ) ) ) ;
4e88e774 Andy McCall 1 month ago ↶
631
Ok ( 0 )
632
}
cdb5beba Andy McCall 1 month ago ↶
633
634
pub fn lock ( path : & str ) -> Result< i32 > {
635
let repo = open ( ) ? ;
636
repo.lock_path ( path) ? ;
94084087 Andy McCall 1 month ago ↶
637
println! ( " {} {} {} " , ui:: done( " Locked" ) , ui:: path( path) , ui:: dim( " (advisory, local)" ) ) ;
cdb5beba Andy McCall 1 month ago ↶
638
Ok ( 0 )
639
}
640
641
pub fn unlock ( path : & str ) -> Result< i32 > {
642
let repo = open ( ) ? ;
643
repo.unlock_path ( path) ? ;
94084087 Andy McCall 1 month ago ↶
644
println! ( " {} {} " , ui:: done( " Unlocked" ) , ui:: path( path) ) ;
cdb5beba Andy McCall 1 month ago ↶
645
Ok ( 0 )
646
}
23b217d7 Andy McCall 1 month ago ↶
647
648
pub fn split ( change : & str , paths : & [String]) -> Result< i32 > {
649
let repo = open ( ) ? ;
650
let h = repo.resolve_change ( change) ? ;
651
let ( h1, h2) = repo.split ( & h, paths) ? ;
652
let c1 = repo.store ( ) .get_change ( & h1) ? ;
653
let c2 = repo.store ( ) .get_change ( & h2) ? ;
94084087 Andy McCall 1 month ago ↶
654
println! ( " {} into {} {} and {} {} " , ui:: done( " Split" ) , ui:: id( short_id ( & c1) ) , ui:: hash( format! ( " #{} " , h1.short ( 12 ) ) ) , ui:: id( short_id ( & c2) ) , ui:: hash( format! ( " #{} " , h2.short ( 12 ) ) ) ) ;
23b217d7 Andy McCall 1 month ago ↶
655
Ok ( 0 )
656
}
7b9809e6 Andy McCall 1 month ago ↶
657
658
fn heads_from ( repo : & Repo, changes : & [String]) -> Result< Vec< oxy_core:: hash:: ChangeHash> > {
659
let mut heads = Vec :: new( ) ;
660
for c in changes {
661
heads.extend ( repo.resolve_heads ( c) ? ) ;
662
}
663
Ok ( heads)
664
}
665
666
pub fn bookmark_create ( name : & str , changes : & [String]) -> Result< i32 > {
667
let repo = open ( ) ? ;
668
repo.snapshot ( " oxy bookmark create" ) ? ;
669
let heads = if changes.is_empty ( ) { None } else { Some ( heads_from ( & repo, changes) ? ) } ;
670
let heads = repo.bookmark_create ( name, heads.as_deref ( ) ) ? ;
671
println! ( " {} bookmark {} {} {} " , ui:: done( " Created" ) , ui:: bookmark( name) , ui:: dim( ui:: glyph( " →" , " ->" ) ) , ui:: dim( plural ( heads.len ( ) , " head" ) ) ) ;
672
println! ( " {} {} " , ui:: note( ) , ui:: dim( format! ( " switch to it with oxy bookmark switch {name} " ) ) ) ;
673
Ok ( 0 )
674
}
675
676
pub fn bookmark_set ( name : & str , changes : & [String]) -> Result< i32 > {
677
let repo = open ( ) ? ;
678
repo.snapshot ( " oxy bookmark set" ) ? ;
679
let heads = heads_from ( & repo, changes) ? ;
680
let heads = repo.bookmark_set ( name, & heads) ? ;
681
println! ( " {} bookmark {} {} {} " , ui:: done( " Moved" ) , ui:: bookmark( name) , ui:: dim( ui:: glyph( " →" , " ->" ) ) , ui:: dim( plural ( heads.len ( ) , " head" ) ) ) ;
682
Ok ( 0 )
683
}
684
685
pub fn bookmark_delete ( name : & str ) -> Result< i32 > {
686
let repo = open ( ) ? ;
687
repo.bookmark_delete ( name) ? ;
688
println! ( " {} bookmark {} {} " , ui:: done( " Deleted" ) , ui:: bookmark( name) , ui:: dim( " (its changes stay until unreachable)" ) ) ;
689
Ok ( 0 )
690
}
691
692
pub fn bookmark_list ( ) -> Result< i32 > {
693
let repo = open ( ) ? ;
694
for b in repo.bookmarks ( ) ? {
695
let mark = if b.current { ui:: ok( ui:: glyph( " ●" , " *" ) ) } else { ui:: dim( ui:: glyph( " ○" , " o" ) ) } ;
696
let mut tail = String :: new( ) ;
697
if let Some ( n) = b.ahead.filter ( | n | * n > 0 ) {
698
tail + = & ui:: ok( format! ( " {} {n} " , ui:: glyph( " ↑" , " +" ) ) ) ;
699
}
700
if let Some ( n) = b.behind.filter ( | n | * n > 0 ) {
701
tail + = & ui:: warn( format! ( " {} {n} " , ui:: glyph( " ↓" , " -" ) ) ) ;
702
}
703
let head = b.heads.first ( ) .map ( | h | repo.store ( ) .get_change ( h) .map ( | c | ui:: subject( subject_of ( & c.message) ) ) .unwrap_or_default ( ) ) .unwrap_or_else ( | | ui:: dim( " (empty)" ) ) ;
704
println! ( " {mark} {} {} {} {tail} {head} " , ui:: bookmark( & b.name) , ui:: sep( ) , ui:: dim( plural ( b.heads.len ( ) , " head" ) ) ) ;
705
}
706
Ok ( 0 )
707
}
708
709
pub fn bookmark_switch ( name : & str , discard : bool ) -> Result< i32 > {
710
let mut repo = open ( ) ? ;
711
let sp = ui:: spinner( & format! ( " Switching to {name} …" ) ) ;
712
let r = repo.bookmark_switch ( name, discard) ? ;
713
sp.done ( & format! ( " {} to {} : {} written, {} removed" , ui:: done( " Switched" ) , ui:: bookmark( name) , plural ( r.written, " file" ) , r.removed) ) ;
714
if r.conflicted {
715
println! ( " {} {} " , ui:: caution( ) , ui:: warn( " this state has conflicts; see oxy status" ) ) ;
716
}
717
Ok ( 0 )
718
}
719
720
pub fn merge ( bookmark : & str ) -> Result< i32 > {
721
let repo = open ( ) ? ;
722
let sp = ui:: spinner( & format! ( " Merging {bookmark} …" ) ) ;
723
let r = repo.merge ( bookmark) ? ;
724
if ! r.changed {
725
sp.done ( & format! ( " {} {} " , ui:: ok( ui:: glyph( " ✔" , " *" ) ) , ui:: dim( format! ( " {} already contains {bookmark} " , repo.config.bookmark) ) ) ) ;
726
return Ok ( 0 ) ;
727
}
728
sp.done ( & format! (
729
" {} {} into {} : now {} {} " ,
730
ui:: done( " Merged" ) ,
731
ui:: bookmark( bookmark) ,
732
ui:: bookmark( & repo.config.bookmark) ,
733
plural ( r.heads.len ( ) , " head" ) ,
734
ui:: dim( if r.checked_out { " ; working copy updated" } else { " ; run oxy checkout to update the working copy" } )
735
) ) ;
736
let snap = repo.snapshot ( " oxy merge" ) ? ;
737
if snap.base_snapshot.is_conflicted ( ) {
738
println! ( " {} {} " , ui:: caution( ) , ui:: warn( " the result has conflicts; see oxy status" ) ) ;
739
}
740
Ok ( 0 )
741
}
742