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} ;
c6aa5bd9 Andy McCall 1 month ago ↶
4
use super :: json;
5
use oxy_core:: repo:: SignatureStatus;
fce44ae9 Andy McCall 1 month ago ↶
6
use super :: open;
135212de Andy McCall 1 month ago ↶
7
use super :: ui;
fce44ae9 Andy McCall 1 month ago ↶
8
use anyhow:: Result;
94084087 Andy McCall 1 month ago ↶
9
use oxy_core:: objects:: ChangeKind;
fce44ae9 Andy McCall 1 month ago ↶
10
use oxy_core:: repo:: { render_change, ChangeStatus, Repo} ;
11
use std:: path:: PathBuf;
12
13
pub fn init ( path : Option< PathBuf> ) -> Result< i32 > {
14
let path = path.unwrap_or ( std:: env:: current_dir( ) ? ) ;
15
let repo = Repo:: init( & path) ? ;
94084087 Andy McCall 1 month ago ↶
16
println! ( " {} oxy repository in {} " , ui:: done( " Initialized" ) , ui:: path( repo.root ( ) .display ( ) ) ) ;
17
println! ( " {} " , ui:: kv( " bookmark" , ui:: bookmark( & repo.config.bookmark) ) ) ;
18
println! ( " {} " , ui:: kv( " author" , format! ( " {} <{} >" , ui:: author( & repo.config.author_name) , repo.config.author_email) ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
19
Ok ( 0 )
20
}
21
22
fn status_tag ( s : ChangeStatus) -> & 'static str {
23
match s {
24
ChangeStatus:: Added => " A" ,
25
ChangeStatus:: Modified => " M" ,
26
ChangeStatus:: Removed => " D" ,
6e723b81 Andy McCall 1 month ago ↶
27
ChangeStatus:: Renamed => " R" ,
fce44ae9 Andy McCall 1 month ago ↶
28
ChangeStatus:: ModeChanged => " X" ,
29
ChangeStatus:: ConflictEdited => " C!" ,
6e723b81 Andy McCall 1 month ago ↶
30
ChangeStatus:: Resolved => " V" ,
fce44ae9 Andy McCall 1 month ago ↶
31
}
32
}
33
50a046e4 Andy McCall 1 month ago ↶
34
pub fn status ( json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
35
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
36
let sp = ui:: spinner( " Snapshotting the working copy…" ) ;
fce44ae9 Andy McCall 1 month ago ↶
37
let snap = repo.snapshot ( " oxy status" ) ? ;
38
let heads = repo.bookmark_heads ( ) ? ;
135212de Andy McCall 1 month ago ↶
39
sp.clear ( ) ;
50a046e4 Andy McCall 1 month ago ↶
40
if json {
41
println! ( " {} " , repo.status_json ( & snap) ? ) ;
42
return Ok ( if snap.base_snapshot.is_conflicted ( ) { 2 } else { 0 } ) ;
43
}
135212de Andy McCall 1 month ago ↶
44
println! (
94084087 Andy McCall 1 month ago ↶
45
" {} {} {} working copy {} {} {} " ,
46
ui:: dim( " On" ) ,
47
ui:: bookmark( & repo.config.bookmark) ,
48
ui:: sep( ) ,
135212de Andy McCall 1 month ago ↶
49
ui:: id( snap.wc.change_id.short ( 8 ) ) ,
94084087 Andy McCall 1 month ago ↶
50
ui:: sep( ) ,
51
ui:: dim( plural ( heads.len ( ) , " head" ) )
135212de Andy McCall 1 month ago ↶
52
) ;
fce44ae9 Andy McCall 1 month ago ↶
53
if snap.statuses.is_empty ( ) {
94084087 Andy McCall 1 month ago ↶
54
println! ( " {} {} " , ui:: ok( ui:: glyph( " ✔" , " *" ) ) , ui:: dim( " nothing to commit, working copy clean" ) ) ;
55
} else {
56
println! ( ) ;
57
for s in & snap.statuses {
58
println! ( " {} {} " , ui:: status_mark( status_tag ( s.status) ) , ui:: path( & s.path) ) ;
59
}
fce44ae9 Andy McCall 1 month ago ↶
60
}
61
if ! snap.base_snapshot.is_conflicted ( ) {
62
return Ok ( 0 ) ;
63
}
135212de Andy McCall 1 month ago ↶
64
println! ( ) ;
94084087 Andy McCall 1 month ago ↶
65
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 ↶
66
for c in & snap.base_snapshot.tree.conflicts {
94084087 Andy McCall 1 month ago ↶
67
println! ( " {} tree: {c:?} " , ui:: err( ui:: glyph( " ✱" , " !" ) ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
68
}
69
for ( p, f) in & snap.base_snapshot.files {
70
if f.is_conflicted ( ) {
71
let state = match snap.statuses.iter ( ) .find ( | s | s.path = = * p ) .map ( | s | s.status ) {
94084087 Andy McCall 1 month ago ↶
72
Some ( ChangeStatus:: Resolved) => ui:: ok( format! ( " {} resolved in working copy" , ui:: glyph( " ✔" , " *" ) ) ) ,
73
Some ( ChangeStatus:: ConflictEdited) => ui:: warn( format! ( " {} markers remain" , ui:: glyph( " ‼" , " !" ) ) ) ,
74
_ => ui:: err( format! ( " {} unresolved" , ui:: glyph( " ✱" , " !" ) ) ) ,
fce44ae9 Andy McCall 1 month ago ↶
75
} ;
135212de Andy McCall 1 month ago ↶
76
println! ( " {} {state} " , ui:: path( p) ) ;
fce44ae9 Andy McCall 1 month ago ↶
77
}
78
}
79
Ok ( 2 )
80
}
81
c6aa5bd9 Andy McCall 1 month ago ↶
82
pub fn commit ( message : & str , depends_on : & [String], paths : & [String], interactive : bool , hunks : & [usize ]) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
83
let repo = open ( ) ? ;
84
let mut deps = Vec :: new( ) ;
85
for d in depends_on {
86
deps.push ( repo.resolve_change ( d) ? ) ;
87
}
c6aa5bd9 Andy McCall 1 month ago ↶
88
let selection = if ! hunks.is_empty ( ) {
89
oxy_core:: repo:: Selection:: Hunks( hunks.to_vec ( ) )
90
} else if interactive {
957fee30 Andy McCall 1 month ago ↶
91
let ( _ , hunks) = repo.hunks ( ) ? ;
92
if hunks.is_empty ( ) {
93
anyhow:: bail! ( " nothing to commit: the working copy matches its base" ) ;
94
}
95
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 ( ) ;
96
let mut chosen = Vec :: new( ) ;
97
let stdin = std:: io:: stdin( ) ;
98
let mut take_rest = false ;
99
for ( n, h) in hunks.iter ( ) .enumerate ( ) {
100
if take_rest {
101
chosen.push ( h.index) ;
102
continue ;
103
}
104
println! ( " {} {} {} " , ui:: section( format! ( " hunk {} of {} " , n + 1 , hunks.len ( ) ) ) , ui:: path( & h.path) , ui:: dim( format! ( " (#{} )" , h.index) ) ) ;
105
for line in h.text.lines ( ) {
106
let styled = if line.starts_with ( ' +' ) { ui:: ok( line) } else if line.starts_with ( ' -' ) { ui:: err( line) } else { ui:: dim( line) } ;
107
println! ( " {styled} " ) ;
108
}
109
loop {
110
print! ( " {} " , ui:: warn( " Commit this hunk? [y]es [n]o [a]ll remaining [q]uit:" ) ) ;
111
use std:: io:: Write as _ ;
112
std:: io:: stdout( ) .flush ( ) ? ;
113
let mut answer = String :: new( ) ;
114
if stdin.read_line ( & mut answer) ? = = 0 {
115
anyhow:: bail! ( " aborted" ) ;
116
}
117
match answer.trim ( ) {
118
" y" | " Y" => {
119
chosen.push ( h.index) ;
120
break ;
121
}
122
" n" | " N" | " " => break ,
123
" a" | " A" => {
124
chosen.push ( h.index) ;
125
take_rest = true ;
126
break ;
127
}
128
" q" | " Q" => anyhow:: bail! ( " aborted; nothing committed" ) ,
129
_ => continue ,
130
}
131
}
132
}
133
if chosen.is_empty ( ) {
134
anyhow:: bail! ( " no hunks chosen; nothing committed" ) ;
135
}
136
oxy_core:: repo:: Selection:: Hunks( chosen)
137
} else if paths.is_empty ( ) {
138
oxy_core:: repo:: Selection:: All
139
} else {
140
oxy_core:: repo:: Selection:: Paths( paths.to_vec ( ) )
141
} ;
135212de Andy McCall 1 month ago ↶
142
let sp = ui:: spinner( " Committing…" ) ;
957fee30 Andy McCall 1 month ago ↶
143
let h = repo.commit_selection ( message, & deps, & selection) ? ;
fce44ae9 Andy McCall 1 month ago ↶
144
let c = repo.store ( ) .get_change ( & h) ? ;
94084087 Andy McCall 1 month ago ↶
145
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 ↶
146
if ! matches! ( selection, oxy_core:: repo:: Selection:: All) {
147
let rest = repo.snapshot ( " oxy commit" ) ? ;
148
if ! rest.statuses.is_empty ( ) {
149
println! ( " {} {} " , ui:: note( ) , ui:: dim( format! ( " {} left uncommitted" , plural ( rest.statuses.len ( ) , " path" ) ) ) ) ;
150
}
151
}
152
Ok ( 0 )
153
}
154
155
pub fn blame ( path : & str , json : bool ) -> Result< i32 > {
156
let repo = open ( ) ? ;
157
let lines = repo.blame ( path) ? ;
158
if json {
159
let items = lines.iter ( ) .map ( | l | {
160
format! (
161
" {{ \" line\" :{} ,\" change_id\" :{} ,\" hash\" :{} ,\" index\" :{} ,\" author\" :{} ,\" when_ms\" :{} }} " ,
162
l.line,
163
oxy_core:: repo:: json_str( & l.change_id.to_hex ( ) ) ,
164
oxy_core:: repo:: json_str( & l.hash.to_hex ( ) ) ,
165
l.index,
166
oxy_core:: repo:: json_str( & l.author) ,
167
l.when_ms
168
)
169
} ) ;
170
println! ( " {} " , oxy_core:: repo:: json_array( items) ) ;
171
return Ok ( 0 ) ;
172
}
173
let width = lines.iter ( ) .map ( | l | l.author.chars ( ) .count ( ) ) .max ( ) .unwrap_or ( 0 ) .min ( 20 ) ;
174
let digits = lines.len ( ) .to_string ( ) .len ( ) ;
175
for l in & lines {
176
let author: String = l.author.chars ( ) .take ( 20 ) .collect ( ) ;
177
let text = String :: from_utf8_lossy( & l.text) ;
178
println! (
179
" {} {} {} {:>digits$}{} {} " ,
180
ui:: id( l.change_id.short ( 8 ) ) ,
181
ui:: author( format! ( " {author:<width$}" ) ) ,
182
ui:: dim( ui:: ago( l.when_ms) ) ,
183
l.line,
184
ui:: dim( ui:: glyph( " │" , " |" ) ) ,
185
text.trim_end_matches ( ' \n ' )
186
) ;
187
}
188
Ok ( 0 )
189
}
190
191
fn bisect_report ( repo : & Repo, step : & oxy_core:: repo:: BisectStep) -> Result< i32 > {
192
use oxy_core:: repo:: BisectStep:: * ;
193
match step {
194
NeedMarks { good, bad } => {
195
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" } ) ) ;
196
Ok ( 0 )
197
}
198
Testing { heads, remaining, steps_left } => {
199
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" ) ) ) ;
200
println! ( " {} {} " , ui:: note( ) , ui:: dim( " run your test, then oxy bisect good | bad | skip" ) ) ;
201
Ok ( 0 )
202
}
203
Found { culprit } => {
204
let c = repo.store ( ) .get_change ( culprit) ? ;
205
println! ( " {} {} {} {} " , ui:: done( " Found" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{} " , culprit.short ( 12 ) ) ) , ui:: subject( subject_of ( & c.message) ) ) ;
206
println! ( " {} {} " , ui:: note( ) , ui:: dim( " the first change whose inclusion makes the state bad; oxy bisect reset to return" ) ) ;
207
Ok ( 0 )
208
}
209
Inconclusive { candidates } => {
210
println! ( " {} {} " , ui:: caution( ) , ui:: warn( format! ( " every remaining candidate was skipped ({} ); oxy bisect reset to return" , plural ( candidates.len ( ) , " change" ) ) ) ) ;
211
Ok ( 1 )
212
}
213
}
214
}
215
216
pub fn bisect_start ( ) -> Result< i32 > {
217
let repo = open ( ) ? ;
218
repo.bisect_start ( ) ? ;
219
println! ( " {} bisect; {} " , ui:: done( " Started" ) , ui:: dim( " now: oxy bisect good CHANGE and oxy bisect bad CHANGE" ) ) ;
220
Ok ( 0 )
221
}
222
223
pub fn bisect_mark ( good : Option< bool > , changes : & [String]) -> Result< i32 > {
224
let repo = open ( ) ? ;
225
let mut heads = Vec :: new( ) ;
226
for c in changes {
227
heads.extend ( repo.resolve_heads ( c) ? ) ;
228
}
229
let step = repo.bisect_mark ( good, & heads) ? ;
230
bisect_report ( & repo, & step)
231
}
232
233
pub fn bisect_run ( command : & [String]) -> Result< i32 > {
234
let repo = open ( ) ? ;
235
let step = repo.bisect_run ( command) ? ;
236
bisect_report ( & repo, & step)
237
}
238
c6aa5bd9 Andy McCall 1 month ago ↶
239
pub fn bisect_status ( as_json : bool ) -> Result< i32 > {
957fee30 Andy McCall 1 month ago ↶
240
let repo = open ( ) ? ;
c6aa5bd9 Andy McCall 1 month ago ↶
241
if as_json {
242
use oxy_core:: repo:: BisectStep:: * ;
243
let v = match repo.bisect_status ( ) ? {
244
None => " null" .to_string ( ) ,
245
Some ( NeedMarks { good, bad } ) => json:: obj( [ ( " state" , json:: s( " need-marks" ) ) , ( " good" , json:: n( good) ) , ( " bad" , json:: n( bad) ) ] ) ,
246
Some ( Testing { heads, remaining, steps_left } ) => json:: obj( [ ( " state" , json:: s( " testing" ) ) , ( " heads" , json:: hashes( & heads) ) , ( " remaining" , json:: n( remaining) ) , ( " steps_left" , json:: n( steps_left) ) ] ) ,
247
Some ( Found { culprit } ) => json:: obj( [ ( " state" , json:: s( " found" ) ) , ( " culprit" , json:: s( & culprit.to_hex ( ) ) ) ] ) ,
248
Some ( Inconclusive { candidates } ) => json:: obj( [ ( " state" , json:: s( " inconclusive" ) ) , ( " candidates" , json:: hashes( & candidates) ) ] ) ,
249
} ;
250
println! ( " {v} " ) ;
251
return Ok ( 0 ) ;
252
}
957fee30 Andy McCall 1 month ago ↶
253
match repo.bisect_status ( ) ? {
254
None => {
255
println! ( " {} " , ui:: dim( " No bisect in progress." ) ) ;
256
Ok ( 0 )
257
}
258
Some ( step) => bisect_report ( & repo, & step) ,
259
}
260
}
261
262
pub fn bisect_reset ( ) -> Result< i32 > {
263
let repo = open ( ) ? ;
264
repo.bisect_reset ( ) ? ;
265
println! ( " {} bisect; working copy restored" , ui:: done( " Reset" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
266
Ok ( 0 )
267
}
268
94084087 Andy McCall 1 month ago ↶
269
270
271
c6aa5bd9 Andy McCall 1 month ago ↶
272
fn signature_json ( repo : & Repo, h : & oxy_core:: hash:: ChangeHash) -> Result< String> {
273
Ok ( match repo.authenticate_change ( h) ? {
274
SignatureStatus:: Unsigned => json:: obj( [ ( " status" , json:: s( " unsigned" ) ) ] ) ,
275
SignatureStatus:: Valid { key, name, email, sealed, revoked } => json:: obj( [
276
( " status" , json:: s( if sealed { " sealed" } else { " signed" } ) ) ,
277
( " key" , json:: s( & key.to_hex ( ) ) ) ,
278
( " name" , json:: s( & name) ) ,
279
( " email" , json:: s( & email) ) ,
280
( " revoked" , json:: b( revoked) ) ,
281
] ) ,
282
SignatureStatus:: Invalid( why) => json:: obj( [ ( " status" , json:: s( " invalid" ) ) , ( " reason" , json:: s( & why) ) ] ) ,
283
} )
284
}
285
286
fn log_entry_json ( repo : & Repo, e : & oxy_core:: repo:: LogEntry) -> Result< String> {
287
let c = & e.change;
288
Ok ( json:: obj( [
289
( " hash" , json:: s( & e.hash.to_hex ( ) ) ) ,
290
( " change_id" , json:: s( & c.change_id.to_hex ( ) ) ) ,
291
( " author" , json:: s( & author_name ( repo, c) ) ) ,
292
( " author_key" , json:: s( & c.author.to_hex ( ) ) ) ,
293
( " when_ms" , json:: n( c.hlc.wall_ms) ) ,
294
( " message" , json:: s( & c.message) ) ,
295
( " kind" , json:: s( & format! ( " {:?} " , c.kind) .to_lowercase ( ) ) ) ,
296
( " deps" , json:: hashes( & c.deps) ) ,
297
( " head" , json:: b( e.is_head) ) ,
298
( " working_copy" , json:: b( e.is_working_copy) ) ,
299
( " signature" , if e.is_working_copy { " null" .into ( ) } else { signature_json ( repo, & e.hash) ? } ) ,
300
] ) )
301
}
302
303
pub fn log ( limit : Option< usize > , verbose : bool , path : Option< & str > , as_json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
304
let repo = open ( ) ? ;
94084087 Andy McCall 1 month ago ↶
305
let snap = repo.snapshot ( " oxy log" ) ? ;
306
let conflicted = snap.base_snapshot.is_conflicted ( ) ;
c6aa5bd9 Andy McCall 1 month ago ↶
307
let entries = match path {
308
Some ( p) => repo.log_for_path ( p) ? ,
309
None => repo.log ( ) ? ,
310
} ;
311
if as_json {
312
let shown = limit.unwrap_or ( entries.len ( ) ) .min ( entries.len ( ) ) ;
313
let mut items = Vec :: new( ) ;
314
for e in entries.iter ( ) .take ( shown) {
315
items.push ( log_entry_json ( & repo, e) ? ) ;
316
}
317
println! ( " {} " , json:: obj( [ ( " bookmark" , json:: s( & repo.config.bookmark) ) , ( " total" , json:: n( entries.len ( ) ) ) , ( " changes" , json:: arr( items) ) ] ) ) ;
318
return Ok ( 0 ) ;
319
}
94084087 Andy McCall 1 month ago ↶
320
let total = entries.len ( ) ;
321
let shown = limit.unwrap_or ( total) .min ( total) ;
322
let rail = ui:: rail( ) ;
323
for ( i, e) in entries.iter ( ) .take ( shown) .enumerate ( ) {
fce44ae9 Andy McCall 1 month ago ↶
324
let c = & e.change;
94084087 Andy McCall 1 month ago ↶
325
let bullet = ui:: log_bullet( e.is_working_copy, e.is_head, e.is_working_copy & & conflicted) ;
326
let mut labels = Vec :: new( ) ;
fce44ae9 Andy McCall 1 month ago ↶
327
if e.is_working_copy {
94084087 Andy McCall 1 month ago ↶
328
labels.push ( ui:: warn( " working copy" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
329
}
94084087 Andy McCall 1 month ago ↶
330
if c.kind = = ChangeKind:: Resolution {
331
labels.push ( ui:: warn( " resolution" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
332
}
94084087 Andy McCall 1 month ago ↶
333
let labels = if labels.is_empty ( ) { String :: new( ) } else { format! ( " {} {} {} " , ui:: dim( " (" ) , labels.join ( ui:: dim( " , " ) .as_str ( ) ) , ui:: dim( " )" ) ) } ;
334
let subject = if e.is_working_copy & & c.message.is_empty ( ) {
335
ui:: dim( if snap.statuses.is_empty ( ) { " (clean)" } else { " (uncommitted changes)" } )
336
} else {
337
ui:: subject( subject_of ( & c.message) )
338
} ;
339
println! ( " {bullet} {} {labels} {subject} " , ui:: id( short_meta_id ( c) ) ) ;
340
let meta = format! (
341
" {} {} {} {} {} {} {} " ,
342
ui:: hash( format! ( " #{} " , e.hash.short ( 12 ) ) ) ,
343
ui:: sep( ) ,
344
ui:: author( author_name ( & repo, c) ) ,
345
ui:: sep( ) ,
346
ui:: dim( ui:: ago( c.hlc.wall_ms) ) ,
347
ui:: sep( ) ,
348
if e.is_working_copy { ui:: dim( " not yet committed" ) } else { signature_mark ( & repo, & e.hash) ? }
349
) ;
350
println! ( " {rail} {meta} " ) ;
351
if verbose {
352
if ! e.is_working_copy {
353
println! ( " {rail} {} " , ui:: dim( format! ( " {} {} " , when ( c.hlc.wall_ms) , signature_line ( & repo, & e.hash) ? ) ) ) ;
354
}
355
if ! c.deps.is_empty ( ) {
356
println! ( " {rail} {} {} " , ui:: dim( ui:: glyph( " ↳" , " ->" ) ) , ui:: dim( deps_of ( & c.deps) ) ) ;
357
}
358
let body: Vec< & str > = c.message.lines ( ) .skip ( 1 ) .skip_while ( | l | l.trim ( ) .is_empty ( ) ) .collect ( ) ;
359
if ! body.is_empty ( ) {
360
println! ( " {rail} " ) ;
361
for line in body {
362
println! ( " {rail} {line} " ) ;
363
}
364
}
fce44ae9 Andy McCall 1 month ago ↶
365
}
94084087 Andy McCall 1 month ago ↶
366
if i + 1 < shown {
367
println! ( " {rail} " ) ;
fce44ae9 Andy McCall 1 month ago ↶
368
}
369
}
94084087 Andy McCall 1 month ago ↶
370
if shown < total {
371
println! ( " {} " , ui:: dim( format! ( " {} {} more; pass -n to see them" , ui:: glyph( " …" , " ..." ) , total - shown) ) ) ;
372
}
fce44ae9 Andy McCall 1 month ago ↶
373
Ok ( 0 )
374
}
375
c6aa5bd9 Andy McCall 1 month ago ↶
376
pub fn diff ( change : Option< & str > , as_json : bool ) -> Result< i32 > {
377
let repo = open ( ) ? ;
378
let hash = change.map ( | c | repo.resolve_change ( c) ) .transpose ( ) ? ;
379
if as_json {
380
let files = repo.diff_files ( hash.as_ref ( ) ) ? ;
381
let items = files.iter ( ) .map ( | f | {
382
json:: obj( [
383
( " path" , json:: s( & f.path) ) ,
384
( " kind" , json:: s( & f.kind) ) ,
385
( " from" , json:: opt( f.from.as_deref ( ) , json:: s) ) ,
386
(
387
" hunks" ,
388
json:: arr( f.hunks.iter ( ) .map ( | h | {
389
json:: obj( [
390
( " base_line" , json:: n( h.base_line) ) ,
391
( " added" , json:: arr( h.added.iter ( ) .map ( | l | json:: s( & String :: from_utf8_lossy( l) ) ) ) ) ,
392
( " removed" , json:: arr( h.removed.iter ( ) .map ( | ( n , l ) | json:: obj( [ ( " line" , json:: n( * n) ) , ( " text" , json:: s( & String :: from_utf8_lossy( l) ) ) ] ) ) ) ) ,
393
] )
394
} ) ) ,
395
) ,
396
] )
397
} ) ;
398
println! ( " {} " , json:: obj( [ ( " change" , json:: opt( hash, | h | json:: s( & h.to_hex ( ) ) ) ) , ( " files" , json:: arr( items) ) ] ) ) ;
399
return Ok ( 0 ) ;
400
}
401
match hash {
402
Some ( h) => {
403
let c = repo.store ( ) .get_change ( & h) ? ;
404
let base = repo.load_state ( & c.deps) ? ;
405
let snapshot = base.materialize ( ) ;
406
print! ( " {} " , render_change ( & c, & base, & snapshot) ) ;
407
}
408
None => {
409
let snap = repo.snapshot ( " oxy diff" ) ? ;
410
print! ( " {} " , render_change ( & snap.wc, & snap.base, & snap.base_snapshot) ) ;
411
}
412
}
413
Ok ( 0 )
414
}
415
416
pub fn cat ( path : & str , change : Option< & str > ) -> Result< i32 > {
417
let repo = open ( ) ? ;
418
let heads = change.map ( | c | repo.resolve_heads ( c) ) .transpose ( ) ? ;
419
let bytes = repo.file_at ( path, heads.as_deref ( ) ) ? ;
420
use std:: io:: Write as _ ;
421
std:: io:: stdout( ) .write_all ( & bytes) ? ;
422
Ok ( 0 )
423
}
424
425
pub fn root ( as_json : bool ) -> Result< i32 > {
426
let Ok ( repo) = Repo:: open( & std:: env:: current_dir( ) ? ) else {
427
if as_json {
428
println! ( " null" ) ;
429
}
430
return Ok ( 1 ) ;
431
} ;
432
if as_json {
433
println! ( " {} " , json:: obj( [ ( " root" , json:: s( & repo.root ( ) .to_string_lossy ( ) ) ) , ( " workspace" , json:: s( & repo.workspace ( ) .to_hex ( ) ) ) , ( " bookmark" , json:: s( & repo.config.bookmark) ) ] ) ) ;
434
} else {
435
println! ( " {} " , repo.root ( ) .display ( ) ) ;
436
}
437
Ok ( 0 )
438
}
439
440
pub fn version ( as_json : bool ) -> Result< i32 > {
441
if as_json {
442
println! ( " {} " , json:: obj( [ ( " version" , json:: s( env! ( " CARGO_PKG_VERSION" ) ) ) , ( " protocol" , json:: n( oxy_core:: protocol:: PROTOCOL_VERSION ) ) , ( " format" , json:: n( 2 ) ) ] ) ) ;
443
} else {
444
println! ( " oxy {} (repository format 2, protocol {} )" , env! ( " CARGO_PKG_VERSION" ) , oxy_core:: protocol:: PROTOCOL_VERSION ) ;
445
}
446
Ok ( 0 )
447
}
448
449
pub fn discard ( paths : & [String], hunks : & [usize ]) -> Result< i32 > {
450
let repo = open ( ) ? ;
451
if paths.is_empty ( ) & & hunks.is_empty ( ) {
452
anyhow:: bail! ( " name paths to restore, or --hunks to undo chosen hunks" ) ;
453
}
454
let mut n = 0 ;
455
if ! hunks.is_empty ( ) {
456
n + = repo.discard_hunks ( hunks) ? ;
457
}
458
if ! paths.is_empty ( ) {
459
n + = repo.discard ( paths) ? ;
460
}
461
println! ( " {} {} restored to the working copy's base" , ui:: done( " Discarded" ) , plural ( n, " path" ) ) ;
462
Ok ( 0 )
463
}
464
465
pub fn hunks ( as_json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
466
let repo = open ( ) ? ;
c6aa5bd9 Andy McCall 1 month ago ↶
467
let ( _ , hunks) = repo.hunks ( ) ? ;
468
if as_json {
469
println! ( " {} " , json:: arr( hunks.iter ( ) .map ( | h | json:: obj( [ ( " index" , json:: n( h.index) ) , ( " path" , json:: s( & h.path) ) , ( " text" , json:: s( & h.text) ) ] ) ) ) ) ;
470
return Ok ( 0 ) ;
471
}
472
if hunks.is_empty ( ) {
473
println! ( " {} " , ui:: dim( " No hunks: the working copy matches its base." ) ) ;
474
}
475
for h in & hunks {
476
println! ( " {} {} " , ui:: section( format! ( " hunk {} " , h.index) ) , ui:: path( & h.path) ) ;
477
for line in h.text.lines ( ) {
478
let styled = if line.starts_with ( ' +' ) { ui:: ok( line) } else if line.starts_with ( ' -' ) { ui:: err( line) } else { ui:: dim( line) } ;
479
println! ( " {styled} " ) ;
480
}
481
}
fce44ae9 Andy McCall 1 month ago ↶
482
Ok ( 0 )
483
}
484
c6aa5bd9 Andy McCall 1 month ago ↶
485
pub fn show ( change : & str , as_json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
486
let repo = open ( ) ? ;
487
repo.snapshot ( " oxy show" ) ? ;
488
let h = repo.resolve_change ( change) ? ;
489
let c = repo.store ( ) .get_change ( & h) ? ;
c6aa5bd9 Andy McCall 1 month ago ↶
490
if as_json {
491
let files = repo.diff_files ( Some ( & h) ) ? ;
492
let heads = repo.bookmark_heads ( ) ? ;
493
let entry = oxy_core:: repo:: LogEntry { hash: h, change: oxy_core:: state:: ChangeMeta:: of( & c) , is_working_copy: c.kind = = oxy_core:: objects:: ChangeKind:: WorkingCopy, is_head: heads.contains ( & h) } ;
494
let mut body = log_entry_json ( & repo, & entry) ? ;
495
body.pop ( ) ;
496
body.push_str ( & format! ( " ,{} :{} }} " , json:: s( " files" ) , json:: arr( files.iter ( ) .map ( | f | json:: obj( [ ( " path" , json:: s( & f.path) ) , ( " kind" , json:: s( & f.kind) ) , ( " from" , json:: opt( f.from.as_deref ( ) , json:: s) ) ] ) ) ) ) ) ;
497
println! ( " {body} " ) ;
498
return Ok ( 0 ) ;
499
}
94084087 Andy McCall 1 month ago ↶
500
println! ( " {} {} {} " , ui:: section( " change" ) , ui:: id( short_id ( & c) ) , ui:: hash( format! ( " #{h} " ) ) ) ;
501
println! ( " {} " , ui:: kv( " kind" , format! ( " {:?} " , c.kind) .to_lowercase ( ) ) ) ;
502
println! ( " {} " , ui:: kv( " author" , ui:: author( author_name ( & repo, & oxy_core:: state:: ChangeMeta:: of( & c) ) ) ) ) ;
503
println! ( " {} " , ui:: kv( " date" , format! ( " {} {} " , when ( c.hlc.wall_ms) , ui:: dim( format! ( " ({} )" , ui:: ago( c.hlc.wall_ms) ) ) ) ) ) ;
504
println! ( " {} " , ui:: kv( " deps" , ui:: dim( deps_line ( & c) ) ) ) ;
505
println! ( " {} " , ui:: kv( " sig" , signature_line ( & repo, & h) ? ) ) ;
506
println! ( ) ;
507
for line in ( if c.message.is_empty ( ) { " (no description)" } else { & c.message } ) .lines ( ) {
508
println! ( " {line} " ) ;
509
}
510
println! ( ) ;
fce44ae9 Andy McCall 1 month ago ↶
511
print! ( " {} " , repo.describe_change ( & h) ? ) ;
512
Ok ( 0 )
513
}
514
515
pub fn checkout ( changes : & [String], discard : bool ) -> Result< i32 > {
516
let repo = open ( ) ? ;
517
let heads = if changes.is_empty ( ) {
518
repo.bookmark_heads ( ) ?
519
} else {
132b884b Andy McCall 1 month ago ↶
520
let mut heads = Vec :: new( ) ;
521
for c in changes {
522
heads.extend ( repo.resolve_heads ( c) ? ) ;
523
}
524
heads
fce44ae9 Andy McCall 1 month ago ↶
525
} ;
526
let spec = if changes.is_empty ( ) { repo.config.bookmark.clone ( ) } else { changes.join ( " " ) } ;
135212de Andy McCall 1 month ago ↶
527
let sp = ui:: spinner( & format! ( " Checking out {spec} …" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
528
let r = repo.checkout ( & heads, discard, & format! ( " oxy checkout {spec} " ) ) ? ;
94084087 Andy McCall 1 month ago ↶
529
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 ↶
530
for n in & r.notes {
531
println! ( " {} {n} " , ui:: note( ) ) ;
532
}
fce44ae9 Andy McCall 1 month ago ↶
533
if r.conflicted {
94084087 Andy McCall 1 month ago ↶
534
println! ( " {} {} " , ui:: caution( ) , ui:: warn( " this state has conflicts; see oxy status" ) ) ;
fce44ae9 Andy McCall 1 month ago ↶
535
}
536
Ok ( 0 )
537
}
538
c6aa5bd9 Andy McCall 1 month ago ↶
539
pub fn resolve ( as_json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
540
let repo = open ( ) ? ;
541
let snap = repo.snapshot ( " oxy resolve" ) ? ;
c6aa5bd9 Andy McCall 1 month ago ↶
542
if as_json {
543
println! ( " {} " , repo.status_json ( & snap) ? ) ;
544
return Ok ( if snap.base_snapshot.is_conflicted ( ) { 2 } else { 0 } ) ;
545
}
fce44ae9 Andy McCall 1 month ago ↶
546
if ! snap.base_snapshot.is_conflicted ( ) {
547
println! ( " The working copy's base has no conflicts." ) ;
548
return Ok ( 0 ) ;
549
}
550
let mut remaining = 0 ;
551
for ( p, f) in & snap.base_snapshot.files {
552
if ! f.is_conflicted ( ) {
553
continue ;
554
}
555
match snap.statuses.iter ( ) .find ( | s | s.path = = * p ) .map ( | s | s.status ) {
556
Some ( ChangeStatus:: Resolved) => println! ( " resolved {p} " ) ,
557
Some ( ChangeStatus:: ConflictEdited) => {
558
remaining + = 1 ;
559
println! ( " markers {p} " ) ;
560
}
561
_ => {
562
remaining + = 1 ;
563
println! ( " unresolved {p} " ) ;
564
}
565
}
566
}
567
for c in & snap.base_snapshot.tree.conflicts {
568
remaining + = 1 ;
569
println! ( " tree {c:?} " ) ;
570
}
571
if remaining = = 0 {
572
println! ( " All conflicts are resolved; `oxy commit` will record the resolution." ) ;
573
Ok ( 0 )
574
} else {
575
Ok ( 2 )
576
}
577
}
578
579
pub fn undo ( ) -> Result< i32 > {
580
let repo = open ( ) ? ;
581
let op = repo.undo ( ) ? ;
94084087 Andy McCall 1 month ago ↶
582
println! ( " {} now at: {} " , ui:: done( " Undone;" ) , ui:: dim( & op.command) ) ;
fce44ae9 Andy McCall 1 month ago ↶
583
Ok ( 0 )
584
}
585
c6aa5bd9 Andy McCall 1 month ago ↶
586
pub fn op_log ( as_json : bool ) -> Result< i32 > {
fce44ae9 Andy McCall 1 month ago ↶
587
let repo = open ( ) ? ;
c6aa5bd9 Andy McCall 1 month ago ↶
588
if as_json {
589
let ops = repo.op_log ( ) ? ;
590
println! (
591
" {} " ,
592
json:: arr( ops.iter ( ) .enumerate ( ) .map ( | ( i , (r , op ) ) | {
593
json:: obj( [
594
( " id" , json:: s( & r.to_hex ( ) ) ) ,
595
( " when_ms" , json:: n( op.hlc.wall_ms) ) ,
596
( " command" , json:: s( & op.command) ) ,
597
( " snapshot" , json:: b( op.command.ends_with ( " (snapshot)" ) ) ) ,
598
( " current" , json:: b( i = = 0 ) ) ,
599
] )
600
} ) )
601
);
602
return Ok ( 0 ) ;
603
}
fce44ae9 Andy McCall 1 month ago ↶
604
for ( i, ( r, op) ) in repo.op_log ( ) ? .into_iter ( ) .enumerate ( ) {
94084087 Andy McCall 1 month ago ↶
605
let bullet = if i = = 0 { ui:: ok( ui:: glyph( " ●" , " @" ) ) } else { ui:: dim( ui:: glyph( " ○" , " o" ) ) } ;
606
let cmd = subject_of ( & op.command) .split ( " \\ n" ) .next ( ) .unwrap_or ( " " ) ;
607
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 ↶
608
}
609
Ok ( 0 )
610
}
d02e545d Andy McCall 1 month ago ↶
611
ba160f60 Andy McCall 1 month ago ↶
612
pub fn gc ( now : bool ) -> Result< i32 > {
d02e545d Andy McCall 1 month ago ↶
613
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
614
let sp = ui:: spinner( " Packing and pruning…" ) ;
ba160f60 Andy McCall 1 month ago ↶
615
let r = repo.gc ( now) ? ;
135212de Andy McCall 1 month ago ↶
616
sp.clear ( ) ;
ba160f60 Andy McCall 1 month ago ↶
617
if r.packed.objects > 0 {
d02e545d Andy McCall 1 month ago ↶
618
println! (
ba160f60 Andy McCall 1 month ago ↶
619
" Packed {} : {} bytes -> {} bytes ({:.0} %)." ,
620
plural ( r.packed.objects, " loose object" ) ,
621
r.packed.raw_bytes,
622
r.packed.packed_bytes,
623
100. 0 * r.packed.packed_bytes as f64 / r.packed.raw_bytes.max ( 1 ) as f64
d02e545d Andy McCall 1 month ago ↶
624
) ;
625
}
135212de Andy McCall 1 month ago ↶
626
println! ( " {} reachable; pruned {} ({} bytes){} {} ; {} present." ,
ba160f60 Andy McCall 1 month ago ↶
627
plural ( r.reachable, " object" ) ,
628
plural ( r.pruned.objects, " unreachable object" ) ,
629
r.pruned.bytes,
630
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 ↶
631
if r.merged_packs > 0 { format! ( " ; merged {} into one" , plural ( r.merged_packs, " pack" ) ) } else { String :: new( ) } ,
ba160f60 Andy McCall 1 month ago ↶
632
plural ( repo.store ( ) .pack_count ( ) , " pack" )
633
) ;
d02e545d Andy McCall 1 month ago ↶
634
Ok ( 0 )
635
}
614541c3 Andy McCall 1 month ago ↶
636
637
638
fn current_head ( repo : & Repo) -> Result< oxy_core:: hash:: ChangeHash> {
639
let snap = repo.snapshot ( " oxy" ) ? ;
640
let base = snap.base.maximal ( & snap.wc.deps) ;
641
match base.as_slice ( ) {
642
[ h] => Ok ( * h) ,
643
_ => anyhow:: bail! ( " the working copy sits on {} heads; name the change explicitly" , base.len ( ) ) ,
644
}
645
}
646
647
pub fn describe ( message : & str , change : Option< & str > ) -> Result< i32 > {
648
let repo = open ( ) ? ;
649
let h = match change {
650
Some ( c) => repo.resolve_change ( c) ? ,
651
None => current_head ( & repo) ? ,
652
} ;
653
let new = repo.describe ( & h, message) ? ;
654
let c = repo.store ( ) .get_change ( & new) ? ;
94084087 Andy McCall 1 month ago ↶
655
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 ↶
656
Ok ( 0 )
657
}
658
23b217d7 Andy McCall 1 month ago ↶
659
pub fn amend ( message : Option< & str > , into : Option< & str > ) -> Result< i32 > {
614541c3 Andy McCall 1 month ago ↶
660
let repo = open ( ) ? ;
23b217d7 Andy McCall 1 month ago ↶
661
let new = match into {
662
Some ( spec) => {
663
let target = repo.resolve_change ( spec) ? ;
664
repo.amend_into ( & target, message) ?
665
}
666
None => repo.amend ( message) ? ,
667
} ;
614541c3 Andy McCall 1 month ago ↶
668
let c = repo.store ( ) .get_change ( & new) ? ;
94084087 Andy McCall 1 month ago ↶
669
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 ↶
670
Ok ( 0 )
671
}
672
673
pub fn abandon ( change : & str ) -> Result< i32 > {
674
let repo = open ( ) ? ;
675
let h = repo.resolve_change ( change) ? ;
676
let c = repo.store ( ) .get_change ( & h) ? ;
6706bdcf Andy McCall 1 month ago ↶
677
let notes = repo.abandon ( & h) ? ;
94084087 Andy McCall 1 month ago ↶
678
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 ↶
679
for n in notes {
94084087 Andy McCall 1 month ago ↶
680
println! ( " {} {n} " , ui:: note( ) ) ;
6706bdcf Andy McCall 1 month ago ↶
681
}
614541c3 Andy McCall 1 month ago ↶
682
Ok ( 0 )
683
}
252e8cb2 Andy McCall 1 month ago ↶
684
685
pub fn checkpoint ( ) -> Result< i32 > {
686
let repo = open ( ) ? ;
135212de Andy McCall 1 month ago ↶
687
let sp = ui:: spinner( " Writing checkpoint…" ) ;
252e8cb2 Andy McCall 1 month ago ↶
688
let ( r, changes, bytes) = repo.checkpoint ( ) ? ;
94084087 Andy McCall 1 month ago ↶
689
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 ↶
690
Ok ( 0 )
691
}
692
693
694805cc Andy McCall 1 month ago ↶
694
pub fn prompt ( format : & str , glyphs : bool , color : Option< & str > ) -> Result< i32 > {
135212de Andy McCall 1 month ago ↶
695
let Ok ( repo) = Repo:: open( & std:: env:: current_dir( ) ? ) else { return Ok ( 1 ) } ;
696
let info = repo.quick_status ( ) ? ;
694805cc Andy McCall 1 month ago ↶
697
let fmt = if glyphs & & format = = oxy_core:: repo:: DEFAULT_PROMPT_FORMAT { oxy_core:: repo:: GLYPH_PROMPT_FORMAT } else { format } ;
698
let line = match color {
699
Some ( " bash" ) => info.format_colored ( fmt, ( " \\ [" , " \\ ]" ) ) ,
700
Some ( " zsh" ) => info.format_colored ( fmt, ( " %{" , " %}" ) ) ,
701
Some ( _ ) => info.format_colored ( fmt, ( " " , " " ) ) ,
702
None => info.format ( fmt) ,
703
} ;
704
println! ( " {line} " ) ;
252e8cb2 Andy McCall 1 month ago ↶
705
Ok ( 0 )
706
}
7421309f Andy McCall 1 month ago ↶
707
708
pub fn sparse ( paths : & [String]) -> Result< i32 > {
709
let mut repo = open ( ) ? ;
710
if paths.is_empty ( ) {
711
if repo.config.sparse_paths.is_empty ( ) {
712
println! ( " No sparse filter: the whole tree is materialized." ) ;
713
} else {
714
println! ( " {} " , ui:: heading( " Sparse filter" ) ) ;
715
for p in & repo.config.sparse_paths {
716
println! ( " {} " , ui:: path( p) ) ;
717
}
718
}
719
return Ok ( 0 ) ;
720
}
721
let snap = repo.snapshot ( " oxy sparse" ) ? ;
722
if ! snap.wc.tree_ops.is_empty ( ) | | ! snap.wc.edits.is_empty ( ) {
723
anyhow:: bail! ( " the working copy has changes; commit them before changing the sparse filter" ) ;
724
}
725
repo.config.set ( " filter.paths" , & paths.join ( " ," ) ) ? ;
726
repo.save_config ( ) ? ;
727
let heads = repo.bookmark_heads ( ) ? ;
728
let sp = ui:: spinner( " Re-checking out with the new filter…" ) ;
729
let r = repo.checkout ( & heads, true , " oxy sparse (checkout)" ) ? ;
730
sp.done ( & format! ( " {} {} : {} written, {} removed" , ui:: ok( " Sparse filter set to" ) , ui:: path( paths.join ( " ," ) ) , plural ( r.written, " file" ) , r.removed) ) ;
731
Ok ( 0 )
732
}
132b884b Andy McCall 1 month ago ↶
733
734
pub fn tag_create ( name : & str , changes : & [String], message : & str ) -> Result< i32 > {
735
let repo = open ( ) ? ;
736
repo.snapshot ( " oxy tag" ) ? ;
737
let heads = if changes.is_empty ( ) {
738
repo.bookmark_heads ( ) ?
739
} else {
740
let mut heads = Vec :: new( ) ;
741
for c in changes {
742
heads.extend ( repo.resolve_heads ( c) ? ) ;
743
}
744
heads
745
} ;
746
let t = repo.tag_create ( name, & heads, message) ? ;
747
println! (
94084087 Andy McCall 1 month ago ↶
748
" {} {} {} {} ({} ){} " ,
749
ui:: done( " Tagged" ) ,
750
ui:: bookmark( & t.name) ,
751
ui:: dim( ui:: glyph( " →" , " ->" ) ) ,
132b884b Andy McCall 1 month ago ↶
752
ui:: hash( format! ( " #{} " , t.state.short ( 12 ) ) ) ,
753
plural ( t.heads.len ( ) , " head" ) ,
754
t.signer.map ( | s | format! ( " , signed by {s} " ) ) .unwrap_or_else ( | | ui:: dim( " , unsigned" ) )
755
) ;
756
Ok ( 0 )
757
}
758
1196b29b Andy McCall 1 month ago ↶
759
pub fn tag_list ( as_json : bool ) -> Result< i32 > {
132b884b Andy McCall 1 month ago ↶
760
let repo = open ( ) ? ;
761
let tags = repo.tags ( ) ? ;
1196b29b Andy McCall 1 month ago ↶
762
if as_json {
763
println! (
764
" {} " ,
765
json:: arr( tags.iter ( ) .map ( | t | {
766
json:: obj( [
767
( " name" , json:: s( & t.name) ) ,
768
( " state" , json:: s( & t.state.to_hex ( ) ) ) ,
769
( " heads" , json:: hashes( & t.heads) ) ,
770
( " message" , json:: s( & t.message) ) ,
771
( " signer" , json:: opt( t.signer.as_ref ( ) , | s | json:: s( & s.to_string ( ) ) ) ) ,
772
] )
773
} ) )
774
) ;
775
return Ok ( 0 ) ;
776
}
132b884b Andy McCall 1 month ago ↶
777
if tags.is_empty ( ) {
778
println! ( " {} " , ui:: dim( " No tags." ) ) ;
779
}
780
for t in tags {
781
println! (
94084087 Andy McCall 1 month ago ↶
782
" {} {} {} {} {} {} " ,
783
ui:: ok( ui:: glyph( " ⚑" , " #" ) ) ,
784
ui:: bookmark( & t.name) ,
132b884b Andy McCall 1 month ago ↶
785
ui:: hash( format! ( " #{} " , t.state.short ( 12 ) ) ) ,
94084087 Andy McCall 1 month ago ↶
786
ui:: sep( ) + & ui:: dim( plural ( t.heads.len ( ) , " head" ) ) ,
787
if t.message.is_empty ( ) { String :: new( ) } else { format! ( " {} " , ui:: subject( subject_of ( & t.message) ) ) } ,
788
t.signer.map ( | s | ui:: sep( ) + & ui:: ok( format! ( " {} {s} " , ui:: glyph( " ✔" , " v" ) ) ) ) .unwrap_or_default ( )
132b884b Andy McCall 1 month ago ↶
789
) ;
790
}
791
Ok ( 0 )
792
}
4e88e774 Andy McCall 1 month ago ↶
793
794
pub fn workspace_add ( path : & std:: path:: Path, changes : & [String]) -> Result< i32 > {
795
let repo = open ( ) ? ;
796
let heads = if changes.is_empty ( ) {
797
None
798
} else {
799
let mut heads = Vec :: new( ) ;
800
for c in changes {
801
heads.extend ( repo.resolve_heads ( c) ? ) ;
802
}
803
Some ( heads)
804
} ;
805
let sp = ui:: spinner( & format! ( " Creating workspace at {} …" , path.display ( ) ) ) ;
806
let ws = repo.workspace_add ( path, heads.as_deref ( ) ) ? ;
94084087 Andy McCall 1 month ago ↶
807
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 ↶
808
Ok ( 0 )
809
}
810
1196b29b Andy McCall 1 month ago ↶
811
pub fn workspace_list ( as_json : bool ) -> Result< i32 > {
4e88e774 Andy McCall 1 month ago ↶
812
let repo = open ( ) ? ;
1196b29b Andy McCall 1 month ago ↶
813
if as_json {
814
println! (
815
" {} " ,
816
json:: arr( repo.workspaces ( ) ? .iter ( ) .map ( | w | {
817
json:: obj( [
818
( " id" , json:: s( & w.id.to_hex ( ) ) ) ,
819
( " path" , json:: s( & w.path.to_string_lossy ( ) ) ) ,
820
( " current" , json:: b( w.current) ) ,
821
( " working_copy" , json:: opt( w.working_copy, | h | json:: s( & h.to_hex ( ) ) ) ) ,
822
] )
823
} ) )
824
) ;
825
return Ok ( 0 ) ;
826
}
4e88e774 Andy McCall 1 month ago ↶
827
for w in repo.workspaces ( ) ? {
828
println! (
829
" {} {} {} {} " ,
94084087 Andy McCall 1 month ago ↶
830
if w.current { ui:: ok( ui:: glyph( " ●" , " *" ) ) } else { ui:: dim( ui:: glyph( " ○" , " o" ) ) } ,
4e88e774 Andy McCall 1 month ago ↶
831
ui:: id( w.id.short ( 8 ) ) ,
832
ui:: path( w.path.display ( ) ) ,
94084087 Andy McCall 1 month ago ↶
833
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 ↶
834
) ;
835
}
836
Ok ( 0 )
837
}
838
839
pub fn workspace_forget ( spec : & str ) -> Result< i32 > {
840
let repo = open ( ) ? ;
841
let all = repo.workspaces ( ) ? ;
842
let target = std:: fs:: canonicalize( spec) .ok ( ) ;
843
let matches: Vec< _ > = all.iter ( ) .filter ( | w | w.id.starts_with_hex ( spec) | | target.as_ref ( ) .is_some_and ( | t | * t = = w.path) ) .collect ( ) ;
844
let w = match matches.as_slice ( ) {
845
[ w] => * w,
846
[ ] => anyhow:: bail! ( " no workspace matches {spec:?}" ) ,
847
_ => anyhow:: bail! ( " {spec:?} is ambiguous" ) ,
848
} ;
849
repo.workspace_forget ( & w.id) ? ;
94084087 Andy McCall 1 month ago ↶
850
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 ↶
851
Ok ( 0 )
852
}
cdb5beba Andy McCall 1 month ago ↶
853
854
pub fn lock ( path : & str ) -> Result< i32 > {
855
let repo = open ( ) ? ;
856
repo.lock_path ( path) ? ;
94084087 Andy McCall 1 month ago ↶
857
println! ( " {} {} {} " , ui:: done( " Locked" ) , ui:: path( path) , ui:: dim( " (advisory, local)" ) ) ;
cdb5beba Andy McCall 1 month ago ↶
858
Ok ( 0 )
859
}
860
861
pub fn unlock ( path : & str ) -> Result< i32 > {
862
let repo = open ( ) ? ;
863
repo.unlock_path ( path) ? ;
94084087 Andy McCall 1 month ago ↶
864
println! ( " {} {} " , ui:: done( " Unlocked" ) , ui:: path( path) ) ;
cdb5beba Andy McCall 1 month ago ↶
865
Ok ( 0 )
866
}
23b217d7 Andy McCall 1 month ago ↶
867
868
pub fn split ( change : & str , paths : & [String]) -> Result< i32 > {
869
let repo = open ( ) ? ;
870
let h = repo.resolve_change ( change) ? ;
871
let ( h1, h2) = repo.split ( & h, paths) ? ;
872
let c1 = repo.store ( ) .get_change ( & h1) ? ;
873
let c2 = repo.store ( ) .get_change ( & h2) ? ;
94084087 Andy McCall 1 month ago ↶
874
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 ↶
875
Ok ( 0 )
876
}
7b9809e6 Andy McCall 1 month ago ↶
877
878
fn heads_from ( repo : & Repo, changes : & [String]) -> Result< Vec< oxy_core:: hash:: ChangeHash> > {
879
let mut heads = Vec :: new( ) ;
880
for c in changes {
881
heads.extend ( repo.resolve_heads ( c) ? ) ;
882
}
883
Ok ( heads)
884
}
885
886
pub fn bookmark_create ( name : & str , changes : & [String]) -> Result< i32 > {
887
let repo = open ( ) ? ;
888
repo.snapshot ( " oxy bookmark create" ) ? ;
889
let heads = if changes.is_empty ( ) { None } else { Some ( heads_from ( & repo, changes) ? ) } ;
890
let heads = repo.bookmark_create ( name, heads.as_deref ( ) ) ? ;
891
println! ( " {} bookmark {} {} {} " , ui:: done( " Created" ) , ui:: bookmark( name) , ui:: dim( ui:: glyph( " →" , " ->" ) ) , ui:: dim( plural ( heads.len ( ) , " head" ) ) ) ;
892
println! ( " {} {} " , ui:: note( ) , ui:: dim( format! ( " switch to it with oxy bookmark switch {name} " ) ) ) ;
893
Ok ( 0 )
894
}
895
896
pub fn bookmark_set ( name : & str , changes : & [String]) -> Result< i32 > {
897
let repo = open ( ) ? ;
898
repo.snapshot ( " oxy bookmark set" ) ? ;
899
let heads = heads_from ( & repo, changes) ? ;
900
let heads = repo.bookmark_set ( name, & heads) ? ;
901
println! ( " {} bookmark {} {} {} " , ui:: done( " Moved" ) , ui:: bookmark( name) , ui:: dim( ui:: glyph( " →" , " ->" ) ) , ui:: dim( plural ( heads.len ( ) , " head" ) ) ) ;
902
Ok ( 0 )
903
}
904
905
pub fn bookmark_delete ( name : & str ) -> Result< i32 > {
906
let repo = open ( ) ? ;
907
repo.bookmark_delete ( name) ? ;
908
println! ( " {} bookmark {} {} " , ui:: done( " Deleted" ) , ui:: bookmark( name) , ui:: dim( " (its changes stay until unreachable)" ) ) ;
909
Ok ( 0 )
910
}
911
c6aa5bd9 Andy McCall 1 month ago ↶
912
pub fn bookmark_list ( as_json : bool ) -> Result< i32 > {
7b9809e6 Andy McCall 1 month ago ↶
913
let repo = open ( ) ? ;
c6aa5bd9 Andy McCall 1 month ago ↶
914
if as_json {
915
let items = repo.bookmarks ( ) ? .into_iter ( ) .map ( | b | {
916
json:: obj( [
917
( " name" , json:: s( & b.name) ) ,
918
( " heads" , json:: hashes( & b.heads) ) ,
919
( " current" , json:: b( b.current) ) ,
920
( " ahead" , json:: opt( b.ahead, json:: n) ) ,
921
( " behind" , json:: opt( b.behind, json:: n) ) ,
922
] )
923
} ) ;
924
println! ( " {} " , json:: arr( items) ) ;
925
return Ok ( 0 ) ;
926
}
7b9809e6 Andy McCall 1 month ago ↶
927
for b in repo.bookmarks ( ) ? {
928
let mark = if b.current { ui:: ok( ui:: glyph( " ●" , " *" ) ) } else { ui:: dim( ui:: glyph( " ○" , " o" ) ) } ;
929
let mut tail = String :: new( ) ;
930
if let Some ( n) = b.ahead.filter ( | n | * n > 0 ) {
931
tail + = & ui:: ok( format! ( " {} {n} " , ui:: glyph( " ↑" , " +" ) ) ) ;
932
}
933
if let Some ( n) = b.behind.filter ( | n | * n > 0 ) {
934
tail + = & ui:: warn( format! ( " {} {n} " , ui:: glyph( " ↓" , " -" ) ) ) ;
935
}
936
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)" ) ) ;
937
println! ( " {mark} {} {} {} {tail} {head} " , ui:: bookmark( & b.name) , ui:: sep( ) , ui:: dim( plural ( b.heads.len ( ) , " head" ) ) ) ;
938
}
939
Ok ( 0 )
940
}
941
942
pub fn bookmark_switch ( name : & str , discard : bool ) -> Result< i32 > {
943
let mut repo = open ( ) ? ;
944
let sp = ui:: spinner( & format! ( " Switching to {name} …" ) ) ;
945
let r = repo.bookmark_switch ( name, discard) ? ;
946
sp.done ( & format! ( " {} to {} : {} written, {} removed" , ui:: done( " Switched" ) , ui:: bookmark( name) , plural ( r.written, " file" ) , r.removed) ) ;
947
if r.conflicted {
948
println! ( " {} {} " , ui:: caution( ) , ui:: warn( " this state has conflicts; see oxy status" ) ) ;
949
}
950
Ok ( 0 )
951
}
952
953
pub fn merge ( bookmark : & str ) -> Result< i32 > {
954
let repo = open ( ) ? ;
955
let sp = ui:: spinner( & format! ( " Merging {bookmark} …" ) ) ;
956
let r = repo.merge ( bookmark) ? ;
957
if ! r.changed {
958
sp.done ( & format! ( " {} {} " , ui:: ok( ui:: glyph( " ✔" , " *" ) ) , ui:: dim( format! ( " {} already contains {bookmark} " , repo.config.bookmark) ) ) ) ;
959
return Ok ( 0 ) ;
960
}
961
sp.done ( & format! (
962
" {} {} into {} : now {} {} " ,
963
ui:: done( " Merged" ) ,
964
ui:: bookmark( bookmark) ,
965
ui:: bookmark( & repo.config.bookmark) ,
966
plural ( r.heads.len ( ) , " head" ) ,
967
ui:: dim( if r.checked_out { " ; working copy updated" } else { " ; run oxy checkout to update the working copy" } )
968
) ) ;
969
let snap = repo.snapshot ( " oxy merge" ) ? ;
970
if snap.base_snapshot.is_conflicted ( ) {
971
println! ( " {} {} " , ui:: caution( ) , ui:: warn( " the result has conflicts; see oxy status" ) ) ;
972
}
973
Ok ( 0 )
974
}
975
728da0f9 Andy McCall 1 month ago ↶
976
pub fn squash ( change : & str , into : & str ) -> Result< i32 > {
977
let repo = open ( ) ? ;
978
let c = repo.resolve_change ( change) ? ;
979
let d = repo.resolve_change ( into) ? ;
980
let sp = ui:: spinner( " Squashing…" ) ;
981
let h = repo.squash ( & c, & d) ? ;
982
let new = repo.store ( ) .get_change ( & h) ? ;
983
sp.done ( & format! ( " {} into {} {} {} " , ui:: done( " Squashed" ) , ui:: id( short_id ( & new) ) , ui:: hash( format! ( " #{} " , h.short ( 12 ) ) ) , ui:: subject( subject_of ( & new.message) ) ) ) ;
984
Ok ( 0 )
985
}
986
987
pub fn rebase ( change : & str , onto : & [String]) -> Result< i32 > {
988
let repo = open ( ) ? ;
989
let c = repo.resolve_change ( change) ? ;
990
let mut heads = Vec :: new( ) ;
991
for o in onto {
992
heads.extend ( repo.resolve_heads ( o) ? ) ;
993
}
994
let sp = ui:: spinner( " Rebasing…" ) ;
995
let h = repo.rebase ( & c, & heads) ? ;
996
let new = repo.store ( ) .get_change ( & h) ? ;
997
sp.done ( & format! ( " {} {} {} {} " , ui:: done( " Rebased" ) , ui:: id( short_id ( & new) ) , ui:: hash( format! ( " #{} " , h.short ( 12 ) ) ) , ui:: dim( format! ( " now depends on {} " , plural ( new.deps.len ( ) , " change" ) ) ) ) ) ;
998
Ok ( 0 )
999
}
1000